From df7d7981b56a4560c95ea7e9b194080e93398ecf Mon Sep 17 00:00:00 2001 From: Matt Windsor Date: Mon, 21 Mar 2011 21:54:31 +0000 Subject: GREAT PACKAGE RESHUFFLE: Everything is now organised into frontend, backend and common (to frontend and backend) packages. Things may have been broken. Doc refresh. --- src/uk/org/ury/backend/config/Auth.java | 36 ++ src/uk/org/ury/backend/config/ConfigReader.java | 115 ++++ src/uk/org/ury/backend/config/Database.java | 45 ++ .../org/ury/backend/database/DatabaseDriver.java | 166 ++++++ src/uk/org/ury/backend/database/DatabaseItem.java | 80 +++ src/uk/org/ury/backend/database/UserClass.java | 38 ++ .../exceptions/ConnectionFailureException.java | 44 ++ .../exceptions/MissingCredentialsException.java | 52 ++ .../exceptions/MissingPropertyException.java | 47 ++ .../database/exceptions/QueryFailureException.java | 45 ++ .../ury/backend/database/exceptions/package.html | 11 + src/uk/org/ury/backend/database/package.html | 15 + .../backend/handlers/LibraryRequestHandler.java | 189 +++++++ .../ury/backend/server/AbstractRequestHandler.java | 254 +++++++++ .../org/ury/backend/server/ApiRequestHandler.java | 42 ++ src/uk/org/ury/backend/server/HttpHandler.java | 205 ++++++++ .../org/ury/backend/server/HttpListenerThread.java | 128 +++++ .../org/ury/backend/server/HttpWorkerThread.java | 104 ++++ src/uk/org/ury/backend/server/Server.java | 99 ++++ .../ury/backend/server/ServerRequestHandler.java | 95 ++++ .../server/exceptions/BadRequestException.java | 47 ++ .../server/exceptions/HandleFailureException.java | 46 ++ .../exceptions/HandlerNotFoundException.java | 51 ++ .../exceptions/HandlerSetupFailureException.java | 55 ++ .../server/exceptions/HandlingException.java | 48 ++ .../server/exceptions/NotAHandlerException.java | 34 ++ .../exceptions/UnknownFunctionException.java | 37 ++ src/uk/org/ury/backend/server/package.html | 23 + src/uk/org/ury/client/Client.java | 60 --- src/uk/org/ury/client/test/ClientTest.java | 57 -- src/uk/org/ury/common/library/LibraryUtils.java | 144 +++++ .../library/exceptions/EmptySearchException.java | 49 ++ .../org/ury/common/library/item/LibraryItem.java | 26 + .../common/library/item/LibraryItemProperty.java | 62 +++ src/uk/org/ury/common/protocol/Directive.java | 18 + src/uk/org/ury/common/protocol/ProtocolUtils.java | 85 +++ src/uk/org/ury/common/protocol/Status.java | 17 + .../exceptions/DecodeFailureException.java | 32 ++ .../exceptions/InvalidMessageException.java | 33 ++ src/uk/org/ury/common/show/ShowChannel.java | 120 +++++ src/uk/org/ury/common/show/ShowUtils.java | 190 +++++++ src/uk/org/ury/common/show/ShowUtils.properties | 4 + src/uk/org/ury/common/show/item/ShowItem.java | 68 +++ .../org/ury/common/show/item/ShowItemProperty.java | 26 + src/uk/org/ury/common/show/item/package.html | 14 + src/uk/org/ury/config/Auth.java | 36 -- src/uk/org/ury/config/ConfigReader.java | 115 ---- src/uk/org/ury/config/Database.java | 45 -- src/uk/org/ury/database/DatabaseDriver.java | 166 ------ src/uk/org/ury/database/DatabaseItem.java | 80 --- src/uk/org/ury/database/UserClass.java | 38 -- .../exceptions/ConnectionFailureException.java | 44 -- .../exceptions/MissingCredentialsException.java | 52 -- .../exceptions/MissingPropertyException.java | 47 -- .../database/exceptions/QueryFailureException.java | 45 -- src/uk/org/ury/database/exceptions/package.html | 11 - src/uk/org/ury/database/package.html | 15 - src/uk/org/ury/frontend/FrontendApplet.java | 2 +- src/uk/org/ury/frontend/FrontendFrame.java | 584 +++++++++------------ src/uk/org/ury/frontend/FrontendModule.java | 42 +- src/uk/org/ury/frontend/client/Client.java | 60 +++ .../org/ury/frontend/client/test/ClientTest.java | 57 ++ .../frontend/cpanels/menu/DemoControlPanel.java | 47 ++ .../frontend/cpanels/menu/demo_control_panel.xml | 7 + .../frontend/cpanels/show/LibraryControlPanel.java | 48 ++ .../cpanels/show/library_control_panel.xml | 9 + .../modules/library/LibraryTableModel.java | 184 +++++++ .../frontend/modules/library/LibraryViewer.java | 159 ++++++ .../modules/library/LibraryViewer.properties | 22 + .../modules/library/LibraryViewerPanel.java | 219 ++++++++ .../modules/library/library_viewer_gui.xml | 32 ++ src/uk/org/ury/frontend/modules/menu/DemoMenu.java | 64 +++ .../ury/frontend/modules/menu/DemoMenuPanel.java | 68 +++ .../ury/frontend/modules/show/ChannelPanel.java | 84 +++ .../org/ury/frontend/modules/show/ShowViewer.java | 118 +++++ .../ury/frontend/modules/show/ShowViewerPanel.java | 133 +++++ src/uk/org/ury/frontend/modules/show/TrackBin.java | 56 ++ .../ury/frontend/modules/show/channel_panel.xml | 36 ++ .../ury/frontend/modules/show/show_viewer_gui.xml | 43 ++ .../frontend/modules/show/show_viewer_gui_old.xml | 46 ++ .../modules/show/show_viewer_gui_panels.xml | 43 ++ src/uk/org/ury/frontend/modules/show/track_bin.xml | 10 + .../ury/frontend/testrig/ApplicationLauncher.java | 27 + src/uk/org/ury/frontend/testrig/Launcher.java | 17 + src/uk/org/ury/library/LibraryRequestHandler.java | 188 ------- src/uk/org/ury/library/LibraryUtils.java | 144 ----- .../library/exceptions/EmptySearchException.java | 49 -- src/uk/org/ury/library/item/LibraryItem.java | 26 - .../org/ury/library/item/LibraryItemProperty.java | 62 --- .../org/ury/library/viewer/LibraryTableModel.java | 184 ------- src/uk/org/ury/library/viewer/LibraryViewer.java | 159 ------ .../ury/library/viewer/LibraryViewer.properties | 22 - .../org/ury/library/viewer/LibraryViewerPanel.java | 218 -------- .../org/ury/library/viewer/library_viewer_gui.xml | 32 -- src/uk/org/ury/protocol/Directive.java | 18 - src/uk/org/ury/protocol/ProtocolUtils.java | 85 --- src/uk/org/ury/protocol/Status.java | 17 - .../exceptions/DecodeFailureException.java | 32 -- .../exceptions/InvalidMessageException.java | 33 -- src/uk/org/ury/server/AbstractRequestHandler.java | 254 --------- src/uk/org/ury/server/ApiRequestHandler.java | 42 -- src/uk/org/ury/server/HttpHandler.java | 205 -------- src/uk/org/ury/server/HttpListenerThread.java | 127 ----- src/uk/org/ury/server/HttpWorkerThread.java | 104 ---- src/uk/org/ury/server/Server.java | 99 ---- src/uk/org/ury/server/ServerRequestHandler.java | 95 ---- .../ury/server/exceptions/BadRequestException.java | 47 -- .../server/exceptions/HandleFailureException.java | 46 -- .../exceptions/HandlerNotFoundException.java | 51 -- .../exceptions/HandlerSetupFailureException.java | 55 -- .../ury/server/exceptions/HandlingException.java | 48 -- .../server/exceptions/NotAHandlerException.java | 34 -- .../exceptions/UnknownFunctionException.java | 37 -- src/uk/org/ury/server/package.html | 23 - src/uk/org/ury/show/ShowChannel.java | 120 ----- src/uk/org/ury/show/ShowUtils.java | 190 ------- src/uk/org/ury/show/ShowUtils.properties | 4 - src/uk/org/ury/show/item/ShowItem.java | 68 --- src/uk/org/ury/show/item/ShowItemProperty.java | 26 - src/uk/org/ury/show/item/package.html | 14 - src/uk/org/ury/show/viewer/ChannelPanel.java | 84 --- .../org/ury/show/viewer/LibraryControlPanel.java | 48 -- src/uk/org/ury/show/viewer/ShowViewer.java | 118 ----- src/uk/org/ury/show/viewer/ShowViewerPanel.java | 133 ----- src/uk/org/ury/show/viewer/TrackBin.java | 56 -- src/uk/org/ury/show/viewer/channel_panel.xml | 36 -- .../org/ury/show/viewer/library_control_panel.xml | 9 - src/uk/org/ury/show/viewer/show_viewer_gui.xml | 43 -- src/uk/org/ury/show/viewer/show_viewer_gui_old.xml | 46 -- .../org/ury/show/viewer/show_viewer_gui_panels.xml | 43 -- src/uk/org/ury/show/viewer/track_bin.xml | 10 - src/uk/org/ury/testrig/ApplicationLauncher.java | 32 -- src/uk/org/ury/testrig/DemoControlPanel.java | 47 -- src/uk/org/ury/testrig/DemoMenu.java | 64 --- src/uk/org/ury/testrig/DemoMenuPanel.java | 90 ---- src/uk/org/ury/testrig/Launcher.java | 17 - src/uk/org/ury/testrig/demo_control_panel.xml | 7 - 137 files changed, 4909 insertions(+), 4999 deletions(-) create mode 100644 src/uk/org/ury/backend/config/Auth.java create mode 100644 src/uk/org/ury/backend/config/ConfigReader.java create mode 100644 src/uk/org/ury/backend/config/Database.java create mode 100644 src/uk/org/ury/backend/database/DatabaseDriver.java create mode 100644 src/uk/org/ury/backend/database/DatabaseItem.java create mode 100644 src/uk/org/ury/backend/database/UserClass.java create mode 100644 src/uk/org/ury/backend/database/exceptions/ConnectionFailureException.java create mode 100644 src/uk/org/ury/backend/database/exceptions/MissingCredentialsException.java create mode 100644 src/uk/org/ury/backend/database/exceptions/MissingPropertyException.java create mode 100644 src/uk/org/ury/backend/database/exceptions/QueryFailureException.java create mode 100644 src/uk/org/ury/backend/database/exceptions/package.html create mode 100644 src/uk/org/ury/backend/database/package.html create mode 100644 src/uk/org/ury/backend/handlers/LibraryRequestHandler.java create mode 100644 src/uk/org/ury/backend/server/AbstractRequestHandler.java create mode 100644 src/uk/org/ury/backend/server/ApiRequestHandler.java create mode 100644 src/uk/org/ury/backend/server/HttpHandler.java create mode 100644 src/uk/org/ury/backend/server/HttpListenerThread.java create mode 100644 src/uk/org/ury/backend/server/HttpWorkerThread.java create mode 100644 src/uk/org/ury/backend/server/Server.java create mode 100644 src/uk/org/ury/backend/server/ServerRequestHandler.java create mode 100644 src/uk/org/ury/backend/server/exceptions/BadRequestException.java create mode 100644 src/uk/org/ury/backend/server/exceptions/HandleFailureException.java create mode 100644 src/uk/org/ury/backend/server/exceptions/HandlerNotFoundException.java create mode 100644 src/uk/org/ury/backend/server/exceptions/HandlerSetupFailureException.java create mode 100644 src/uk/org/ury/backend/server/exceptions/HandlingException.java create mode 100644 src/uk/org/ury/backend/server/exceptions/NotAHandlerException.java create mode 100644 src/uk/org/ury/backend/server/exceptions/UnknownFunctionException.java create mode 100644 src/uk/org/ury/backend/server/package.html delete mode 100644 src/uk/org/ury/client/Client.java delete mode 100644 src/uk/org/ury/client/test/ClientTest.java create mode 100644 src/uk/org/ury/common/library/LibraryUtils.java create mode 100644 src/uk/org/ury/common/library/exceptions/EmptySearchException.java create mode 100644 src/uk/org/ury/common/library/item/LibraryItem.java create mode 100644 src/uk/org/ury/common/library/item/LibraryItemProperty.java create mode 100644 src/uk/org/ury/common/protocol/Directive.java create mode 100644 src/uk/org/ury/common/protocol/ProtocolUtils.java create mode 100644 src/uk/org/ury/common/protocol/Status.java create mode 100644 src/uk/org/ury/common/protocol/exceptions/DecodeFailureException.java create mode 100644 src/uk/org/ury/common/protocol/exceptions/InvalidMessageException.java create mode 100644 src/uk/org/ury/common/show/ShowChannel.java create mode 100644 src/uk/org/ury/common/show/ShowUtils.java create mode 100644 src/uk/org/ury/common/show/ShowUtils.properties create mode 100644 src/uk/org/ury/common/show/item/ShowItem.java create mode 100644 src/uk/org/ury/common/show/item/ShowItemProperty.java create mode 100644 src/uk/org/ury/common/show/item/package.html delete mode 100644 src/uk/org/ury/config/Auth.java delete mode 100644 src/uk/org/ury/config/ConfigReader.java delete mode 100644 src/uk/org/ury/config/Database.java delete mode 100644 src/uk/org/ury/database/DatabaseDriver.java delete mode 100644 src/uk/org/ury/database/DatabaseItem.java delete mode 100644 src/uk/org/ury/database/UserClass.java delete mode 100644 src/uk/org/ury/database/exceptions/ConnectionFailureException.java delete mode 100644 src/uk/org/ury/database/exceptions/MissingCredentialsException.java delete mode 100644 src/uk/org/ury/database/exceptions/MissingPropertyException.java delete mode 100644 src/uk/org/ury/database/exceptions/QueryFailureException.java delete mode 100644 src/uk/org/ury/database/exceptions/package.html delete mode 100644 src/uk/org/ury/database/package.html create mode 100644 src/uk/org/ury/frontend/client/Client.java create mode 100644 src/uk/org/ury/frontend/client/test/ClientTest.java create mode 100644 src/uk/org/ury/frontend/cpanels/menu/DemoControlPanel.java create mode 100644 src/uk/org/ury/frontend/cpanels/menu/demo_control_panel.xml create mode 100644 src/uk/org/ury/frontend/cpanels/show/LibraryControlPanel.java create mode 100644 src/uk/org/ury/frontend/cpanels/show/library_control_panel.xml create mode 100644 src/uk/org/ury/frontend/modules/library/LibraryTableModel.java create mode 100644 src/uk/org/ury/frontend/modules/library/LibraryViewer.java create mode 100644 src/uk/org/ury/frontend/modules/library/LibraryViewer.properties create mode 100644 src/uk/org/ury/frontend/modules/library/LibraryViewerPanel.java create mode 100644 src/uk/org/ury/frontend/modules/library/library_viewer_gui.xml create mode 100644 src/uk/org/ury/frontend/modules/menu/DemoMenu.java create mode 100644 src/uk/org/ury/frontend/modules/menu/DemoMenuPanel.java create mode 100644 src/uk/org/ury/frontend/modules/show/ChannelPanel.java create mode 100644 src/uk/org/ury/frontend/modules/show/ShowViewer.java create mode 100644 src/uk/org/ury/frontend/modules/show/ShowViewerPanel.java create mode 100644 src/uk/org/ury/frontend/modules/show/TrackBin.java create mode 100644 src/uk/org/ury/frontend/modules/show/channel_panel.xml create mode 100644 src/uk/org/ury/frontend/modules/show/show_viewer_gui.xml create mode 100644 src/uk/org/ury/frontend/modules/show/show_viewer_gui_old.xml create mode 100644 src/uk/org/ury/frontend/modules/show/show_viewer_gui_panels.xml create mode 100644 src/uk/org/ury/frontend/modules/show/track_bin.xml create mode 100644 src/uk/org/ury/frontend/testrig/ApplicationLauncher.java create mode 100644 src/uk/org/ury/frontend/testrig/Launcher.java delete mode 100644 src/uk/org/ury/library/LibraryRequestHandler.java delete mode 100644 src/uk/org/ury/library/LibraryUtils.java delete mode 100644 src/uk/org/ury/library/exceptions/EmptySearchException.java delete mode 100644 src/uk/org/ury/library/item/LibraryItem.java delete mode 100644 src/uk/org/ury/library/item/LibraryItemProperty.java delete mode 100644 src/uk/org/ury/library/viewer/LibraryTableModel.java delete mode 100644 src/uk/org/ury/library/viewer/LibraryViewer.java delete mode 100644 src/uk/org/ury/library/viewer/LibraryViewer.properties delete mode 100644 src/uk/org/ury/library/viewer/LibraryViewerPanel.java delete mode 100644 src/uk/org/ury/library/viewer/library_viewer_gui.xml delete mode 100644 src/uk/org/ury/protocol/Directive.java delete mode 100644 src/uk/org/ury/protocol/ProtocolUtils.java delete mode 100644 src/uk/org/ury/protocol/Status.java delete mode 100644 src/uk/org/ury/protocol/exceptions/DecodeFailureException.java delete mode 100644 src/uk/org/ury/protocol/exceptions/InvalidMessageException.java delete mode 100644 src/uk/org/ury/server/AbstractRequestHandler.java delete mode 100644 src/uk/org/ury/server/ApiRequestHandler.java delete mode 100644 src/uk/org/ury/server/HttpHandler.java delete mode 100644 src/uk/org/ury/server/HttpListenerThread.java delete mode 100644 src/uk/org/ury/server/HttpWorkerThread.java delete mode 100644 src/uk/org/ury/server/Server.java delete mode 100644 src/uk/org/ury/server/ServerRequestHandler.java delete mode 100644 src/uk/org/ury/server/exceptions/BadRequestException.java delete mode 100644 src/uk/org/ury/server/exceptions/HandleFailureException.java delete mode 100644 src/uk/org/ury/server/exceptions/HandlerNotFoundException.java delete mode 100644 src/uk/org/ury/server/exceptions/HandlerSetupFailureException.java delete mode 100644 src/uk/org/ury/server/exceptions/HandlingException.java delete mode 100644 src/uk/org/ury/server/exceptions/NotAHandlerException.java delete mode 100644 src/uk/org/ury/server/exceptions/UnknownFunctionException.java delete mode 100644 src/uk/org/ury/server/package.html delete mode 100644 src/uk/org/ury/show/ShowChannel.java delete mode 100644 src/uk/org/ury/show/ShowUtils.java delete mode 100644 src/uk/org/ury/show/ShowUtils.properties delete mode 100644 src/uk/org/ury/show/item/ShowItem.java delete mode 100644 src/uk/org/ury/show/item/ShowItemProperty.java delete mode 100644 src/uk/org/ury/show/item/package.html delete mode 100644 src/uk/org/ury/show/viewer/ChannelPanel.java delete mode 100644 src/uk/org/ury/show/viewer/LibraryControlPanel.java delete mode 100644 src/uk/org/ury/show/viewer/ShowViewer.java delete mode 100644 src/uk/org/ury/show/viewer/ShowViewerPanel.java delete mode 100644 src/uk/org/ury/show/viewer/TrackBin.java delete mode 100644 src/uk/org/ury/show/viewer/channel_panel.xml delete mode 100644 src/uk/org/ury/show/viewer/library_control_panel.xml delete mode 100644 src/uk/org/ury/show/viewer/show_viewer_gui.xml delete mode 100644 src/uk/org/ury/show/viewer/show_viewer_gui_old.xml delete mode 100644 src/uk/org/ury/show/viewer/show_viewer_gui_panels.xml delete mode 100644 src/uk/org/ury/show/viewer/track_bin.xml delete mode 100644 src/uk/org/ury/testrig/ApplicationLauncher.java delete mode 100644 src/uk/org/ury/testrig/DemoControlPanel.java delete mode 100644 src/uk/org/ury/testrig/DemoMenu.java delete mode 100644 src/uk/org/ury/testrig/DemoMenuPanel.java delete mode 100644 src/uk/org/ury/testrig/Launcher.java delete mode 100644 src/uk/org/ury/testrig/demo_control_panel.xml (limited to 'src') diff --git a/src/uk/org/ury/backend/config/Auth.java b/src/uk/org/ury/backend/config/Auth.java new file mode 100644 index 0000000..46660e5 --- /dev/null +++ b/src/uk/org/ury/backend/config/Auth.java @@ -0,0 +1,36 @@ +package uk.org.ury.backend.config; + +/** + * A login authorisation configuration + * + * @author Nathan Lasseter + */ +public class Auth { + + private String user; + private String pass; + + /** + * Get the username of the login + * + * @return String username + */ + public String getUser() { return user; } + /** + * Get the password of the login + * + * @return String password + */ + public String getPass() { return pass; } + + /** + * Create a login auth object + * @param user The username for the login + * @param pass The password for the login + */ + public Auth(String user, String pass) { + this.user = user; + this.pass = pass; + } + +} \ No newline at end of file diff --git a/src/uk/org/ury/backend/config/ConfigReader.java b/src/uk/org/ury/backend/config/ConfigReader.java new file mode 100644 index 0000000..63403c9 --- /dev/null +++ b/src/uk/org/ury/backend/config/ConfigReader.java @@ -0,0 +1,115 @@ +package uk.org.ury.backend.config; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import uk.org.ury.backend.database.exceptions.MissingCredentialsException; + +/** + * Reads in an XML config file and creates config objects + * + * @author Nathan Lasseter + */ +public class ConfigReader { + + private Database database = null; + private Auth roAuth = null; + private Auth rwAuth = null; + + /** + * Get the database configuration + * + * @return Database database + */ + public Database getDatabase() { return database; } + /** + * Get the read only login auth configuration + * + * @return Auth roAauth + */ + public Auth getRoAuth() { return roAuth; } + /** + * Get the read write login auth configuration + * + * @return Auth rwAauth + */ + public Auth getRwAuth() { return rwAuth; } + + /** + * Read in the config file and create the Database and Auth configuration objects. + * Specify a config file. + * @throws MissingCredentialsException + */ + public ConfigReader(String configFile) throws MissingCredentialsException { + + try { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + Document doc = docBuilder.parse (configFile); + doc.getDocumentElement().normalize(); + + NodeList nList = doc.getElementsByTagName("auth"); + for(int i = 0; i < nList.getLength(); i++) { + Node nNode = nList.item(i); + if(nNode.getNodeType() == Node.ELEMENT_NODE) { + Element eElement = (Element) nNode; + String user = getTagValue("user", eElement); + String pass = getTagValue("pass", eElement); + String sType = getTagValue("type", eElement); + if(sType.equalsIgnoreCase("read_only")) { + if(roAuth != null) continue; + roAuth = new Auth(user, pass); + } else if(sType.equalsIgnoreCase("read_write")) { + if(rwAuth != null) continue; + rwAuth = new Auth(user, pass); + } else { + throw new IllegalArgumentException("Unused user class."); + } + } + } + + nList = doc.getElementsByTagName("database"); + for(int i = 0; i < nList.getLength(); i++) { + if(database != null) break; + Node nNode = nList.item(i); + if(nNode.getNodeType() == Node.ELEMENT_NODE) { + Element eElement = (Element) nNode; + String host = getTagValue("host", eElement); + String port = getTagValue("port", eElement); + String db = getTagValue("db", eElement); + database = new Database(host, Integer.parseInt(port.trim()), db); + } + } + } + + catch(NullPointerException n) { + throw new MissingCredentialsException("An element node is empty."); + } + + catch (SAXParseException err) { + System.out.println ("** Parsing error" + ", line " + err.getLineNumber () + ", uri " + err.getSystemId ()); + System.out.println(" " + err.getMessage ()); + } + catch (SAXException e) { + Exception x = e.getException (); + ((x == null) ? e : x).printStackTrace (); + } + catch (Throwable t) { + t.printStackTrace (); + } + } + + private static String getTagValue(String sTag, Element eElement){ + NodeList nlList= eElement.getElementsByTagName(sTag).item(0).getChildNodes(); + Node nValue = (Node) nlList.item(0); + + return nValue.getNodeValue(); + } +} diff --git a/src/uk/org/ury/backend/config/Database.java b/src/uk/org/ury/backend/config/Database.java new file mode 100644 index 0000000..1939a4a --- /dev/null +++ b/src/uk/org/ury/backend/config/Database.java @@ -0,0 +1,45 @@ +package uk.org.ury.backend.config; + +/** + * A Database Server configuration + * + * @author Nathan Lasseter + */ +public class Database { + + private String host; + private int port; + private String db; + + /** + * Get the hostname of the database server + * + * @return String hostname + */ + public String getHost() { return host; } + /** + * Get the port the database server is running on + * + * @return int port + */ + public int getPort() { return port; } + /** + * Get the name of the database + * + * @return String database name + */ + public String getDb() { return db; } + + /** + * Create a database object + * @param host The hostname of the database server + * @param port The port that the database server listens on + * @param db The name of the database on the server + */ + public Database(String host, int port, String db) { + this.host = host; + this.port = port; + this.db = db; + } + +} \ No newline at end of file diff --git a/src/uk/org/ury/backend/database/DatabaseDriver.java b/src/uk/org/ury/backend/database/DatabaseDriver.java new file mode 100644 index 0000000..5cd9b98 --- /dev/null +++ b/src/uk/org/ury/backend/database/DatabaseDriver.java @@ -0,0 +1,166 @@ +package uk.org.ury.backend.database; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import uk.org.ury.backend.config.ConfigReader; +import uk.org.ury.backend.database.exceptions.ConnectionFailureException; +import uk.org.ury.backend.database.exceptions.MissingCredentialsException; + +/** + * A database connection manager that connects to the URY databases using + * suitably privileged accounts, and handles the processing of SQL queries. + * + * @author Matt Windsor + * @author Nathan Lasseter + * + */ +public class DatabaseDriver { + /* The JDBC path used to connect to the URY database. */ + private String DATABASE_PATH = "jdbc:postgresql://"; + + /* The database connection. */ + private Connection conn; + + /** + * Construct a new DatabaseDriver with the given user class. + * + * @param config + * The config with login details. + * + * @param type + * The user class to log in to the database with. + * + * @throws IllegalArgumentException + * if the user class is not supported (this should not happen). + * + * @throws MissingCredentialsException + * if the user class login credentials could not be loaded. + * + * @throws ConnectionFailureException + * if the database backend failed to connect to the database + * server. + */ + public DatabaseDriver(ConfigReader config, UserClass type) + throws MissingCredentialsException, ConnectionFailureException { + try { + connect(config, type); + } catch (SQLException e) { + throw new ConnectionFailureException(e.getMessage()); + } + + } + + /** + * Connect to the URY database. + * + * @param config + * The config to use for the connection. + * @param type + * The access level of the connection + * + * @throws SQLException + * if the database connection failed. + */ + private void connect(ConfigReader config, UserClass type) + throws SQLException { + if (config == null) + throw new IllegalArgumentException("Supplied null config."); + + if (config.getDatabase().getHost() == null) + throw new IllegalArgumentException("config has no associated host."); + + if (config.getDatabase().getDb() == null) + throw new IllegalArgumentException( + "config has no associated database."); + + DATABASE_PATH = DATABASE_PATH + config.getDatabase().getHost() + "/" + + config.getDatabase().getDb(); + + if (type == UserClass.READ_ONLY) { + if (config.getRoAuth().getUser() == null) + throw new IllegalArgumentException( + "config has no associated username."); + if (config.getRoAuth().getPass() == null) + throw new IllegalArgumentException( + "config has no associated password."); + conn = DriverManager.getConnection(DATABASE_PATH, config + .getRoAuth().getUser(), config.getRoAuth().getPass()); + } else if (type == UserClass.READ_WRITE) { + if (config.getRwAuth().getUser() == null) + throw new IllegalArgumentException( + "config has no associated username."); + if (config.getRwAuth().getPass() == null) + throw new IllegalArgumentException( + "config has no associated password."); + conn = DriverManager.getConnection(DATABASE_PATH, config + .getRwAuth().getUser(), config.getRwAuth().getPass()); + } + } + + /** + * Execute an unprepared SQL statement with no arguments. + * + * @param sql + * The SQL statement to execute. + * @param fetchSize + * The maximum number of query rows to return. + * + * @return the JDBC results set. + * + * @throws SQLException + * if a SQL error occurs. + */ + public ResultSet executeQuery(String sql, int fetchSize) + throws SQLException { + Statement st = conn.createStatement(); + st.setFetchSize(fetchSize); + + return st.executeQuery(sql); + } + + /** + * Perform a SQL statement with arguments. + * + * This accepts an array of parameter objects, which must each either be + * String or Integer objects. The objects will be used sequentially to fill + * in '?' placeholders in the query text. + * + * @param sql + * The SQL statement to execute. + * @param params + * A list of parameter objects. + * @param fetchSize + * The maximum number of query rows to return. + * + * @return the set of results from the query. + * + * @throws IllegalArgumentException + * if any of the parameters is unsupported by the database as a + * statement parameter. + * + * @throws SQLException + * if a SQL error occurs. + */ + public ResultSet executeQuery(String sql, Object[] params, int fetchSize) + throws SQLException { + PreparedStatement st = conn.prepareStatement(sql); + + st.setFetchSize(fetchSize); + + for (int i = 0; i < params.length; i++) + if (params[i] instanceof String) + st.setString(i + 1, (String) params[i]); + else if (params[i] instanceof Integer) + st.setInt(i + 1, (Integer) params[i]); + else + throw new IllegalArgumentException("Unsupported parameter #" + + (i + 1)); + + return st.executeQuery(); + } +} diff --git a/src/uk/org/ury/backend/database/DatabaseItem.java b/src/uk/org/ury/backend/database/DatabaseItem.java new file mode 100644 index 0000000..b25e9f2 --- /dev/null +++ b/src/uk/org/ury/backend/database/DatabaseItem.java @@ -0,0 +1,80 @@ +package uk.org.ury.backend.database; + +import java.util.HashMap; +import java.util.Map; + +import uk.org.ury.backend.database.exceptions.MissingPropertyException; + +/** + * An abstract class presenting a template for objects serving as a data + * structure for collections of properties retrieved from a SQL database. + * + * @param E + * The enumeration type used as the property list. + * + * @param T + * The type of datum stored for each property. + * + * @author Matt Windsor + */ +public abstract class DatabaseItem { + private Map properties; + + /** + * Construct a new item from an existing list of properties. + * + * @param properties + * The map of properties that the new item will inherit. + */ + public DatabaseItem(Map properties) { + this.properties = properties; + } + + /** + * Check whether a property has been set in the item. + * + * @return true if the property has been set; false otherwise. + */ + + public boolean has(E property) { + return properties.containsKey(property); + } + + /** + * Query this item for a property. + * + * @param property + * The property to query. + * + * @return The property, if it exists. + * + * @throws MissingPropertyException + * if the property does not exist. + */ + public T get(E property) throws MissingPropertyException { + if (properties.containsKey(property)) + return properties.get(property); + else + throw new MissingPropertyException(property.toString()); + } + + /** + * Retrieve a map of string representations of the properties. + * + * This relies on E and T having meaningful toString methods. + * + * @return a list of lines representing the response. + */ + public Map asResponse() { + // TODO: Fan out implementation details into separate class + Map response = new HashMap(); + + for (E property : properties.keySet()) { + if (properties.get(property) != null) + response.put(property.toString(), properties.get(property) + .toString()); + } + + return response; + } +} diff --git a/src/uk/org/ury/backend/database/UserClass.java b/src/uk/org/ury/backend/database/UserClass.java new file mode 100644 index 0000000..1cd39fc --- /dev/null +++ b/src/uk/org/ury/backend/database/UserClass.java @@ -0,0 +1,38 @@ +/** + * + */ +package uk.org.ury.backend.database; + +/** + * The various user classes of the database driver. + * + * These refer to various users in the database proper, and thus grant various + * levels of permission to the program. + * + * Please use the least privileged user class that works. For most cases, + * READ_ONLY should work perfectly. + * + * @author Matt Windsor + * + */ + +public enum UserClass { + // Constant configName + READ_ONLY ("read_only"), + READ_WRITE ("read_write"); + + /** + * The name of the tag in the configuration file that contains the + * credentials for this user class. + */ + public String configName; + + /** + * Constructs a new UserClass. + * + * @param configName The name of the user class in the config. + */ + private UserClass(String configName) { + this.configName = configName; + } +} diff --git a/src/uk/org/ury/backend/database/exceptions/ConnectionFailureException.java b/src/uk/org/ury/backend/database/exceptions/ConnectionFailureException.java new file mode 100644 index 0000000..393f3ce --- /dev/null +++ b/src/uk/org/ury/backend/database/exceptions/ConnectionFailureException.java @@ -0,0 +1,44 @@ +/** + * + */ +package uk.org.ury.backend.database.exceptions; + +/** + * Exception thrown when the database backend fails to connect to + * the database server, in absence of a more specific exception. + * + * @author Matt Windsor + */ + +public class ConnectionFailureException extends Exception +{ + /** + * + */ + private static final long serialVersionUID = -7353531873142099828L; + + +/** + * Construct a new ConnectionFailureException with a + * default reason. + */ + + public + ConnectionFailureException () + { + super ("Connection failure."); + } + + + /** + * Construct a new ConnectionFailureException. + * + * @param reason The explanation for the exception. + */ + + public + ConnectionFailureException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/backend/database/exceptions/MissingCredentialsException.java b/src/uk/org/ury/backend/database/exceptions/MissingCredentialsException.java new file mode 100644 index 0000000..4c73683 --- /dev/null +++ b/src/uk/org/ury/backend/database/exceptions/MissingCredentialsException.java @@ -0,0 +1,52 @@ +/** + * + */ +package uk.org.ury.backend.database.exceptions; + +/** + * Exception thrown when the database credentials required to + * log into the URY database under a user class are missing, + * and thus the log-in cannot continue. + * + * The best practice for handling a MissingCredentialsException + * is to attempt to log into the database with a less privileged + * user class or, if the credentials for read-only access are + * missing, give up. + * + * @author Matt Windsor + */ + +public class MissingCredentialsException extends Exception +{ + + /** + * + */ + + private static final long serialVersionUID = -397479334359858162L; + + + /** + * Construct a new MissingCredentialsException with a + * default reason. + */ + + public + MissingCredentialsException () + { + super ("Missing credentials."); + } + + + /** + * Construct a new MissingCredentialsException. + * + * @param reason The explanation for the exception. + */ + + public + MissingCredentialsException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/backend/database/exceptions/MissingPropertyException.java b/src/uk/org/ury/backend/database/exceptions/MissingPropertyException.java new file mode 100644 index 0000000..485771b --- /dev/null +++ b/src/uk/org/ury/backend/database/exceptions/MissingPropertyException.java @@ -0,0 +1,47 @@ +/** + * + */ +package uk.org.ury.backend.database.exceptions; + + +/** + * Exception thrown when a DatabaseItem is queried for a property + * that does not exist. + * + * This is (usually) not a fatal error. + * + * @author Matt Windsor + */ + +public class MissingPropertyException extends Exception +{ + /** + * + */ + private static final long serialVersionUID = -7353531873142099828L; + + +/** + * Construct a new MissingPropertyException with a + * default reason. + */ + + public + MissingPropertyException () + { + super ("Query failure."); + } + + + /** + * Construct a new MissingPropertyException. + * + * @param reason The explanation for the exception. + */ + + public + MissingPropertyException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/backend/database/exceptions/QueryFailureException.java b/src/uk/org/ury/backend/database/exceptions/QueryFailureException.java new file mode 100644 index 0000000..6c01e61 --- /dev/null +++ b/src/uk/org/ury/backend/database/exceptions/QueryFailureException.java @@ -0,0 +1,45 @@ +/** + * + */ +package uk.org.ury.backend.database.exceptions; + + +/** + * Exception thrown when the database backend fails to execute + * a query. + * + * @author Matt Windsor + */ + +public class QueryFailureException extends Exception +{ + /** + * + */ + private static final long serialVersionUID = -7353531873142099828L; + + +/** + * Construct a new QueryFailureException with a + * default reason. + */ + + public + QueryFailureException () + { + super ("Query failure."); + } + + + /** + * Construct a new QueryFailureException. + * + * @param reason The explanation for the exception. + */ + + public + QueryFailureException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/backend/database/exceptions/package.html b/src/uk/org/ury/backend/database/exceptions/package.html new file mode 100644 index 0000000..b5e7cac --- /dev/null +++ b/src/uk/org/ury/backend/database/exceptions/package.html @@ -0,0 +1,11 @@ + + + + + uk.org.ury.database.exceptions + + +

Exceptions thrown by the database services classes.

+ + \ No newline at end of file diff --git a/src/uk/org/ury/backend/database/package.html b/src/uk/org/ury/backend/database/package.html new file mode 100644 index 0000000..2b138b8 --- /dev/null +++ b/src/uk/org/ury/backend/database/package.html @@ -0,0 +1,15 @@ + + + + + uk.org.ury.database + + +

Database services for the URY Presenter Suite.

+

The classes provided within this package are expected to be + used by the back-end through utility classes, and not + by the frontend, which should use the server API to indirectly + query the database.

+ + \ No newline at end of file diff --git a/src/uk/org/ury/backend/handlers/LibraryRequestHandler.java b/src/uk/org/ury/backend/handlers/LibraryRequestHandler.java new file mode 100644 index 0000000..25423f6 --- /dev/null +++ b/src/uk/org/ury/backend/handlers/LibraryRequestHandler.java @@ -0,0 +1,189 @@ +/** + * + */ +package uk.org.ury.backend.handlers; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.entity.StringEntity; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.json.simple.JSONValue; + +import uk.org.ury.backend.database.DatabaseDriver; +import uk.org.ury.backend.database.UserClass; +import uk.org.ury.backend.database.exceptions.ConnectionFailureException; +import uk.org.ury.backend.database.exceptions.MissingCredentialsException; +import uk.org.ury.backend.database.exceptions.QueryFailureException; +import uk.org.ury.backend.server.AbstractRequestHandler; +import uk.org.ury.backend.server.Server; +import uk.org.ury.backend.server.exceptions.BadRequestException; +import uk.org.ury.backend.server.exceptions.HandleFailureException; +import uk.org.ury.backend.server.exceptions.HandlerNotFoundException; +import uk.org.ury.backend.server.exceptions.HandlerSetupFailureException; +import uk.org.ury.backend.server.exceptions.NotAHandlerException; +import uk.org.ury.backend.server.exceptions.UnknownFunctionException; +import uk.org.ury.common.library.LibraryUtils; +import uk.org.ury.common.library.exceptions.EmptySearchException; +import uk.org.ury.common.library.item.LibraryItem; +import uk.org.ury.common.protocol.Directive; +import uk.org.ury.common.protocol.Status; + +/** + * A request handler for library queries. + * + * @author Matt Windsor + */ +public class LibraryRequestHandler extends AbstractRequestHandler { + /** + * Construct a new LibraryRequestHandler. + * + * @param server + * The instance of the URY server responsible for the request. + * + * @param mount + * The directory to which this handler is to be mounted. + */ + public LibraryRequestHandler(Server server, String mount) { + super(server, mount); + } + + /** + * Perform a library search, populating the response list. + * + * @param parameters + * A key-value map of parameters supplied with the server + * request. Typically, the function parameter will detail the + * function that the request handler is expected to perform. + * + * @param response + * The response list to populate. + * + * @param server + * The server providing database resources. + * + * @throws HandleFailureException + * if an error occurs that thwarts the handling of the request. + */ + + private void doSearch(Map parameters, + Map response, Server server) + throws HandleFailureException { + if (parameters.containsKey("search") == false) + throw new HandleFailureException("Search term is missing."); + else if (parameters.get("search") == null) + throw new HandleFailureException("Search term is null."); + + String search = parameters.get("search"); + DatabaseDriver dd = null; + + try { + dd = server.getDatabaseConnection(UserClass.READ_ONLY); + } catch (MissingCredentialsException e) { + throw new HandleFailureException(e.getMessage()); + } catch (ConnectionFailureException e) { + throw new HandleFailureException(e.getMessage()); + } + + try { + List> itemArray = new ArrayList>(); + + for (LibraryItem li : LibraryUtils.search(dd, search)) { + itemArray.add(li.asResponse()); + } + + response.put(Directive.ITEMS.toString(), itemArray); + } catch (QueryFailureException e) { + throw new HandleFailureException(e.getMessage()); + } catch (EmptySearchException e) { + throw new HandleFailureException(e.getMessage()); + } + } + + /** + * Handle a HTTP GET request. + * + * @param request + * The HTTP request. + * + * @param response + * The response that the handler will populate during the + * handling of the request. + * + * @param context + * The HTTP context. + * + * @throws HandlerNotFoundException + * if the client requested a request handler that could not be + * found on the class path. + * + * @throws HandlerSetupFailureException + * if the handler was found but could not be set up (eg does not + * implement appropriate interface or cannot be instantiated). + * + * @throws HandleFailureException + * if an appropriate handler was contacted, but it failed to + * process the request. + * + * @throws BadRequestException + * if the request was malformed or invalid. + * + * @throws NotAHandlerException + * if the class requested to handle the request is not a + * handler. + * + * @throws UnknownFunctionException + * if the request is for a path that does not correspond to one + * of this handler's functions. + */ + @Override + public void handleGet(HttpRequest request, HttpResponse response, + HttpContext context) throws HandlerNotFoundException, + HandlerSetupFailureException, HandleFailureException, + BadRequestException, NotAHandlerException, UnknownFunctionException { + String path; + String uri = request.getRequestLine().getUri(); + String query = getQueryString(uri); + + if (query == null) + path = uri.toLowerCase(Locale.ENGLISH); + else + path = uri.split("\\?" + query)[0].toLowerCase(Locale.ENGLISH); + + Map content = new HashMap(); + + if (path.equals("/library/tracks")) { + try { + doSearch(parseQueryString(query), content, server); + } catch (UnsupportedEncodingException e) { + throw new HandleFailureException(e.getMessage()); + } + } else { + throw new UnknownFunctionException(path); + } + + response.setStatusLine(request.getProtocolVersion(), HttpStatus.SC_OK, + "OK"); + + content.put(Directive.STATUS.toString(), Status.OK.toString()); + + StringEntity entity = null; + + try { + entity = new StringEntity(JSONValue.toJSONString(content)); + } catch (UnsupportedEncodingException e) { + throw new HandlerSetupFailureException(getClass().getName(), e); + } + + entity.setContentType(HTTP.PLAIN_TEXT_TYPE); + response.setEntity(entity); + } +} diff --git a/src/uk/org/ury/backend/server/AbstractRequestHandler.java b/src/uk/org/ury/backend/server/AbstractRequestHandler.java new file mode 100644 index 0000000..53517d7 --- /dev/null +++ b/src/uk/org/ury/backend/server/AbstractRequestHandler.java @@ -0,0 +1,254 @@ +/** + * + */ +package uk.org.ury.backend.server; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.entity.StringEntity; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestHandler; +import org.json.simple.JSONValue; + +import uk.org.ury.backend.server.exceptions.BadRequestException; +import uk.org.ury.backend.server.exceptions.HandleFailureException; +import uk.org.ury.backend.server.exceptions.HandlerNotFoundException; +import uk.org.ury.backend.server.exceptions.HandlerSetupFailureException; +import uk.org.ury.backend.server.exceptions.HandlingException; +import uk.org.ury.backend.server.exceptions.NotAHandlerException; +import uk.org.ury.backend.server.exceptions.UnknownFunctionException; +import uk.org.ury.common.protocol.Directive; +import uk.org.ury.common.protocol.Status; + +/** + * An abstract request handler for HttpCore, providing basic functionality such + * as uniform error response. + * + * @author Matt Windsor + */ +public abstract class AbstractRequestHandler implements HttpRequestHandler { + protected Server server; + protected String mount; + + /** + * Constructs a new AbstractRequestHandler. + * + * Obviously, this class cannot be instantiated directly. + * + * @param server + * The instance of the URY server responsible for the request. + * + * @param mount + * The directory to which this handler is to be mounted. + */ + public AbstractRequestHandler(Server server, String mount) { + this.server = server; + this.mount = mount; + } + + /** + * Begins handling of a HTTP request. + * + * @param request + * The HTTP request. + * + * @param response + * The response that the handler will populate during the + * handling of the request. + * + * @param context + * The HTTP context. + */ + @Override + public void handle(HttpRequest request, HttpResponse response, + HttpContext context) throws HttpException, IOException { + String method = request.getRequestLine().getMethod() + .toUpperCase(Locale.ENGLISH); + + if (method.equals("GET")) { + try { + handleGet(request, response, context); + } catch (HandlerNotFoundException e) { + // TODO: log + serveError(request, response, HttpStatus.SC_NOT_FOUND, + e.getMessage()); + } catch (BadRequestException e) { + // TODO: log + serveError(request, response, HttpStatus.SC_BAD_REQUEST, + e.getMessage()); + } catch (HandlingException e) { + serveError(request, response, + HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } + } else { + serveError(request, response, HttpStatus.SC_NOT_IMPLEMENTED, + "Method not implemented."); + } + } + + /** + * Serves a HTTP plain-text error as the HTTP response for a request. + * + * @param request + * The request that is being responded to. + * + * @param response + * The response to populate with the error message. + * + * @param code + * HTTP status code to use. + * + * @param reason + * The reason to display to the client. + */ + protected void serveError(HttpRequest request, HttpResponse response, + int code, String reason) { + // Get the reason string to put in the error response. + String statusReason = ""; + + switch (code) { + case HttpStatus.SC_BAD_REQUEST: + statusReason = "Bad Request"; + break; + case HttpStatus.SC_NOT_FOUND: + statusReason = "Not Found"; + break; + default: + case HttpStatus.SC_INTERNAL_SERVER_ERROR: + statusReason = "Internal Server Error"; + break; + } + + response.setStatusLine(request.getProtocolVersion(), code, statusReason); + StringEntity entity = null; + + try { + Map content = new HashMap(); + + content.put(Directive.STATUS.toString(), Status.ERROR.toString()); + content.put(Directive.REASON.toString(), reason); + + entity = new StringEntity(JSONValue.toJSONString(content)); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (entity != null) { + entity.setContentType(HTTP.PLAIN_TEXT_TYPE); + response.setEntity(entity); + } + } + + /** + * Handles a HTTP GET request. + * + * @param request + * The HTTP request. + * + * @param response + * The response that the handler will populate during the + * handling of the request. + * + * @param context + * The HTTP context. + * + * @throws HandlerNotFoundException + * if the client requested a request handler that could not be + * found on the class path. + * + * @throws HandlerSetupFailureException + * if the handler was found but could not be set up (eg does not + * implement appropriate interface or cannot be instantiated). + * + * @throws HandleFailureException + * if an appropriate handler was contacted, but it failed to + * process the request. + * + * @throws BadRequestException + * if the request was malformed or invalid. + * + * @throws NotAHandlerException + * if the class requested to handle the request is not a + * handler. + * + * @throws UnknownFunctionException + * if the request is for a path that does not correspond to one + * of this handler's functions. + */ + protected abstract void handleGet(HttpRequest request, + HttpResponse response, HttpContext context) + throws HandlerNotFoundException, HandlerSetupFailureException, + HandleFailureException, BadRequestException, NotAHandlerException, + UnknownFunctionException; + + /** + * Parses a query string, populating a key-value map of the URL-unescaped + * results. + * + * @param query + * The query string to parse. + * + * @return A map associating parameter keys and values. + * + * @throws UnsupportedEncodingException + * if the URL decoder fails. + */ + protected final Map parseQueryString(String query) + throws UnsupportedEncodingException { + Map params = new HashMap(); + + // At least one parameter + if (query != null && query.endsWith("&") == false) { + String[] qsplit = { query }; + + // More than one parameter - split the query. + if (query.contains("&")) + qsplit = query.split("&"); + + for (String param : qsplit) { + // Has a value + if (param.contains("=") && param.endsWith("=") == false) { + String[] paramsplit = param.split("="); + params.put(URLDecoder.decode(paramsplit[0], "UTF-8"), + URLDecoder.decode(paramsplit[1], "UTF-8")); + } + // Doesn't have a value + else if (param.contains("=") == false) { + params.put(URLDecoder.decode(param, "UTF-8"), null); + } + } + } + + return params; + } + + /** + * Gets the query string element of a URI. + * + * @param uri + * The Uniform Resource Indicator whose query string should be + * extracted. + * + * @return The query string, or null if it does not exist. + */ + protected final String getQueryString(String uri) { + String result = null; + + if (uri.contains("?") && uri.endsWith("?") == false) { + result = uri.split("\\?")[1]; + } + + return result; + } +} \ No newline at end of file diff --git a/src/uk/org/ury/backend/server/ApiRequestHandler.java b/src/uk/org/ury/backend/server/ApiRequestHandler.java new file mode 100644 index 0000000..fc867ce --- /dev/null +++ b/src/uk/org/ury/backend/server/ApiRequestHandler.java @@ -0,0 +1,42 @@ +package uk.org.ury.backend.server; + +import java.util.Map; + +import uk.org.ury.backend.server.exceptions.HandleFailureException; + + +/** + * Interface for classes that can handle requests addressed to their + * class name from the main server. + * + * For an example of how to implement a RequestHandler, see + * ServerRequestHandler. + * + * @author Matt Windsor + */ +public interface ApiRequestHandler +{ + /** + * Handle a server GET request (that is, a request for data + * output). + * + * @param parameters A key-value map of parameters supplied with the + * server request. Typically, the "function" + * parameter will detail the function that the + * request handler is expected to perform. + * + * @param server The server from which the request originated. + * This will be able to provide the handler with + * pooled resources, for example the database. + * + * @return A series of key-value pairs to pass back to + * the client. + * + * @throws HandleFailureException if the handler cannot + * handle the request. + */ + + public Map + handleGetRequest (Map parameters, Server server) + throws HandleFailureException; +} diff --git a/src/uk/org/ury/backend/server/HttpHandler.java b/src/uk/org/ury/backend/server/HttpHandler.java new file mode 100644 index 0000000..ed94d3d --- /dev/null +++ b/src/uk/org/ury/backend/server/HttpHandler.java @@ -0,0 +1,205 @@ +/* + * HttpHandler.java + * --------------------- + * + * Part of the URY Server Platform + * + * V0.00 2011/03/20 + * + * (C) 2011 URY Computing + * + * Based on the HttpCore example code, which is available under the + * Apache License, version 2.0; the copyright notice provided with + * said code follows. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package uk.org.ury.backend.server; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; + +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.entity.StringEntity; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestHandler; +import org.json.simple.JSONValue; + +import uk.org.ury.backend.server.exceptions.BadRequestException; +import uk.org.ury.backend.server.exceptions.HandleFailureException; +import uk.org.ury.backend.server.exceptions.HandlerNotFoundException; +import uk.org.ury.backend.server.exceptions.HandlerSetupFailureException; +import uk.org.ury.backend.server.exceptions.NotAHandlerException; +import uk.org.ury.common.protocol.Directive; +import uk.org.ury.common.protocol.Status; + +/** + * @author Matt Windsor, Apache Software Foundation + */ +public class HttpHandler extends AbstractRequestHandler implements + HttpRequestHandler { + + /** + * Construct a new HttpHandler. + * + * @param server + * The instance of the URY server responsible for the request. + * + * @param mount + * The directory to which this handler is to be mounted. + */ + public HttpHandler(Server server, String mount) { + super(server, mount); + } + + /** + * Handle a HTTP GET request. + * + * @param request + * The HTTP request. + * + * @param response + * The response that the handler will populate during the + * handling of the request. + * + * @param context + * The HTTP context. + * + * @throws HandlerNotFoundException + * if the client requested a request handler that could not be + * found on the class path. + * + * @throws HandlerSetupFailureException + * if the handler was found but could not be set up (eg does not + * implement appropriate interface or cannot be instantiated). + * + * @throws HandleFailureException + * if an appropriate handler was contacted, but it failed to + * process the request. + * + * @throws BadRequestException + * if the request was malformed or invalid. + * + * @throws NotAHandlerException + * if the class requested to handle the request is not a + * handler. + */ + @Override + public void handleGet(HttpRequest request, HttpResponse response, + HttpContext context) throws HandlerNotFoundException, + HandlerSetupFailureException, HandleFailureException, + BadRequestException, NotAHandlerException { + String path = request.getRequestLine().getUri(); + + if (path.equals("/index.html") || path.equals("/")) { + // Someone's trying to get the index page! + // Humour them. + + response.setStatusLine(request.getProtocolVersion(), + HttpStatus.SC_OK, "OK"); + + StringEntity entity = null; + + try { + entity = new StringEntity(Server.DOCTYPE + Server.INDEX_HTML); + } catch (UnsupportedEncodingException e) { + throw new HandlerSetupFailureException("(Index page)", e); + } + + entity.setContentType("text/html"); + + response.setEntity(entity); + } else { + // Convert this into a URL and fan out the various parts of it. + + URL pathURL = null; + + try { + pathURL = new URL("http://localhost" + path); + } catch (MalformedURLException e) { + throw new BadRequestException(e); + } + + String className = "uk.org.ury" + + pathURL.getPath().replace('/', '.'); + System.out.println(className); + Class newClass = null; + + try { + newClass = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new HandlerNotFoundException(className, e); + } + + if (ApiRequestHandler.class.isAssignableFrom(newClass) == false) + throw new NotAHandlerException(className); + + String queryString = pathURL.getQuery(); + Map parameters; + + try { + parameters = parseQueryString(queryString); + } catch (UnsupportedEncodingException e) { + throw new HandlerSetupFailureException(className, e); + } + + Map content = null; + + try { + ApiRequestHandler srh = ((ApiRequestHandler) newClass + .newInstance()); + content = srh.handleGetRequest(parameters, server); + } catch (InstantiationException e) { + throw new HandlerSetupFailureException(className, e); + } catch (IllegalAccessException e) { + throw new HandlerSetupFailureException(className, e); + } + + // Everything seems OK, so make the response. + + response.setStatusLine(request.getProtocolVersion(), + HttpStatus.SC_OK, "OK"); + + content.put(Directive.STATUS.toString(), Status.OK.toString()); + + StringEntity entity = null; + + try { + entity = new StringEntity(JSONValue.toJSONString(content)); + } catch (UnsupportedEncodingException e) { + throw new HandlerSetupFailureException(className, e); + } + + entity.setContentType(HTTP.PLAIN_TEXT_TYPE); + response.setEntity(entity); + } + } +} diff --git a/src/uk/org/ury/backend/server/HttpListenerThread.java b/src/uk/org/ury/backend/server/HttpListenerThread.java new file mode 100644 index 0000000..c14ddb9 --- /dev/null +++ b/src/uk/org/ury/backend/server/HttpListenerThread.java @@ -0,0 +1,128 @@ +/* + * HttpListenerThread.java + * ----------------------- + * + * Part of the URY Server Platform + * + * V0.00 2011/03/20 + * + * (C) 2011 URY Computing + * + * Based on the HttpCore example code, which is available under the + * Apache License, version 2.0; the copyright notice provided with + * said code follows. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package uk.org.ury.backend.server; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.impl.DefaultConnectionReuseStrategy; +import org.apache.http.impl.DefaultHttpResponseFactory; +import org.apache.http.impl.DefaultHttpServerConnection; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.params.HttpParams; +import org.apache.http.params.SyncBasicHttpParams; +import org.apache.http.protocol.HttpProcessor; +import org.apache.http.protocol.HttpRequestHandlerRegistry; +import org.apache.http.protocol.HttpService; +import org.apache.http.protocol.ImmutableHttpProcessor; +import org.apache.http.protocol.ResponseConnControl; +import org.apache.http.protocol.ResponseContent; +import org.apache.http.protocol.ResponseDate; +import org.apache.http.protocol.ResponseServer; + +import uk.org.ury.backend.handlers.LibraryRequestHandler; + +/** + * Listener thread for the URY server HTTP interface. + * + * @author Matt Windsor + * @author Apache Software Foundation + */ +public class HttpListenerThread extends Thread { + private ServerSocket ssocket; + private HttpParams params; + private HttpProcessor httpproc; + private HttpRequestHandlerRegistry registry; + private HttpService service; + + public HttpListenerThread(int port, Server server) throws IOException { + ssocket = new ServerSocket(port); + params = new SyncBasicHttpParams(); + + params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000) + .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, + 8 * 1024) + .setBooleanParameter( + CoreConnectionPNames.STALE_CONNECTION_CHECK, false) + .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true) + .setParameter(CoreProtocolPNames.ORIGIN_SERVER, + "HttpComponents/1.1"); + + httpproc = new ImmutableHttpProcessor(new HttpResponseInterceptor[] { + new ResponseDate(), new ResponseServer(), + new ResponseContent(), new ResponseConnControl() }); + + registry = new HttpRequestHandlerRegistry(); + registry.register("/library/*", new LibraryRequestHandler(server, + "/library")); + registry.register("*", new HttpHandler(server, "")); + + service = new HttpService(httpproc, + new DefaultConnectionReuseStrategy(), + new DefaultHttpResponseFactory(), registry, params); + } + + /** + * Thread execution body. + */ + @Override + public void run() { + while (Thread.interrupted() == false) { + Socket csocket = null; + DefaultHttpServerConnection conn = new DefaultHttpServerConnection(); + Thread thread = null; + + try { + csocket = ssocket.accept(); + conn.bind(csocket, params); + } catch (IOException e) { + e.printStackTrace(); + break; + } + + thread = new HttpWorkerThread(service, conn); + thread.setDaemon(true); + thread.start(); + } + } +} diff --git a/src/uk/org/ury/backend/server/HttpWorkerThread.java b/src/uk/org/ury/backend/server/HttpWorkerThread.java new file mode 100644 index 0000000..563832a --- /dev/null +++ b/src/uk/org/ury/backend/server/HttpWorkerThread.java @@ -0,0 +1,104 @@ +/* + * HttpWorkerThread.java + * --------------------- + * + * Part of the URY Server Platform + * + * V0.00 2011/03/20 + * + * (C) 2011 URY Computing + * + * Based on the HttpCore example code, which is available under the + * Apache License, version 2.0; the copyright notice provided with + * said code follows. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package uk.org.ury.backend.server; + +import java.io.IOException; +import java.io.InterruptedIOException; + +import org.apache.http.ConnectionClosedException; +import org.apache.http.HttpException; +import org.apache.http.HttpServerConnection; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpService; + +/** + * A worker thread in the server HTTP interface. + * + * This thread handles requests from the connected client, passing them to the + * request handler(s). + * + * @author Matt Windsor, Apache Software Foundation + */ +public class HttpWorkerThread extends Thread { + private final HttpService service; + private final HttpServerConnection conn; + + /** + * Construct a new HttpWorkerThread. + * + * @param service + * The HTTP service the thread is working for. + * @param conn + * The connection the thread is listening on. + */ + public HttpWorkerThread(HttpService service, HttpServerConnection conn) { + super(); + this.service = service; + this.conn = conn; + } + + /** + * Thread execution body. + */ + public void run() { + HttpContext context = new BasicHttpContext(null); + + try { + while (Thread.interrupted() == false && conn.isOpen()) { + service.handleRequest(conn, context); + } + } catch (ConnectionClosedException e) { + System.out.println("Client closed connection."); + } catch (InterruptedIOException e) { + System.out.println("Interrupted IO: " + e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + } catch (HttpException e) { + e.printStackTrace(); + } finally { + try { + conn.shutdown(); + } catch (IOException e) { + // Ignore + } + } + } +} diff --git a/src/uk/org/ury/backend/server/Server.java b/src/uk/org/ury/backend/server/Server.java new file mode 100644 index 0000000..57a0030 --- /dev/null +++ b/src/uk/org/ury/backend/server/Server.java @@ -0,0 +1,99 @@ +/* + * Server.java + * ----------- + * + * Part of the URY Server Platform + * + * V0.00 2011/03/20 + * + * (C) 2011 URY Computing + */ + +package uk.org.ury.backend.server; + +import java.io.IOException; + +import uk.org.ury.backend.config.ConfigReader; +import uk.org.ury.backend.database.DatabaseDriver; +import uk.org.ury.backend.database.UserClass; +import uk.org.ury.backend.database.exceptions.ConnectionFailureException; +import uk.org.ury.backend.database.exceptions.MissingCredentialsException; + +/** + * The unified URY server, accepting requests over HTTP. + * + * @author Matt Windsor + * @version 2011.0320 + */ +public class Server { + public static final String SERVER_VERSION = "SLUT 0.0"; + public static final String DOCTYPE = ""; + public static final String INDEX_HTML = "\n" + "\n " + + "\n " + SERVER_VERSION + "" + "\n " + + "\n " + "\n

Welcome to the " + SERVER_VERSION + + " server

" + + "\n

This server exposes a class-based API for accessing" + + "\n the internals of the " + SERVER_VERSION + " system.

" + + "\n

See the documentation for details.

" + "\n " + + "\n"; + + /** + * The main method, which serves to create a server. + * + * @param args + * The argument vector. + */ + public static void main(String[] args) { + Server srv = new Server(); + srv.run(); + } + + /** + * Run the server. + */ + private void run() { + Thread thread = null; + + try { + thread = new HttpListenerThread(8000, this); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + + thread.setDaemon(false); + thread.run(); + } + + /** + * Gets a database connection using the given user class. + * + * @param userClass + * The user class to get a connection for. + * + * @return a database connection, which may or may not have been created on + * this call. + * + * @throws MissingCredentialsException + * if the credentials for the given userclass are missing. + * + * @throws ConnectionFailureException + * if the connection failed. + */ + public DatabaseDriver getDatabaseConnection(UserClass userClass) + throws MissingCredentialsException, ConnectionFailureException { + // TODO: Singleton + + ConfigReader config = new ConfigReader("res/conf.xml"); + + return new DatabaseDriver(config, UserClass.READ_ONLY); + } + + /** + * @return the version string of the server. + */ + public String getVersion() { + return SERVER_VERSION; + } +} diff --git a/src/uk/org/ury/backend/server/ServerRequestHandler.java b/src/uk/org/ury/backend/server/ServerRequestHandler.java new file mode 100644 index 0000000..82764f2 --- /dev/null +++ b/src/uk/org/ury/backend/server/ServerRequestHandler.java @@ -0,0 +1,95 @@ +/** + * + */ +package uk.org.ury.backend.server; + +import java.util.HashMap; +import java.util.Map; + +import uk.org.ury.backend.server.ApiRequestHandler; +import uk.org.ury.backend.server.Server; +import uk.org.ury.backend.server.exceptions.HandleFailureException; + + +/** + * A request handler for server queries. + * + * @author Matt Windsor + */ + +public class ServerRequestHandler implements ApiRequestHandler +{ + /** + * Handle a server GET request (that is, a request for data + * output). + * + * @param parameters A key-value map of parameters supplied with + * the server request. Typically, the function + * parameter will detail the function that the + * request handler is expected to perform. + * + * @param server The server from which the request originated. + * This will be able to provide the handler with + * pooled resources, for example the database. + * + * @return A list of lines to return in the body of the + * server's response to the client. + * + * @throws HandleFailureException if the handler cannot + * handle the request. + */ + + @Override + public Map + handleGetRequest (Map parameters, Server server) + throws HandleFailureException + { + Map response = new HashMap (); + + if (parameters.containsKey ("function")) + { + String function = parameters.get ("function"); + + if (function.equals ("info")) + { + getInfo (response, server); + } + else if (function.equals ("help")) + { + response.put ("INFO", "Available functions:"); + response.put ("INFO", "info - Get server information."); + } + else if (function.equals ("test")) + response.put ("INFO", "Test succeeded."); + else + throw new HandleFailureException ("Unknown function: " + + function + ". (Try 'function=help'.)"); + + } + else + throw new HandleFailureException ("No function provided. (Try 'function=help'.)"); + + return response; + } + + + /** + * Retrieve information about the server. + * + * @param response The response list to populate. + * + * @param server The server providing database resources. + * + * @throws HandleFailureException if an error occurs + * that thwarts the handling of the request. + */ + + private void + getInfo (Map response, Server server) + throws HandleFailureException + { + response.put ("INFO", "University Radio York BAPS Replacement"); + response.put ("INFO", "Server version is " + server.getVersion ()); + } + +} diff --git a/src/uk/org/ury/backend/server/exceptions/BadRequestException.java b/src/uk/org/ury/backend/server/exceptions/BadRequestException.java new file mode 100644 index 0000000..6f200f3 --- /dev/null +++ b/src/uk/org/ury/backend/server/exceptions/BadRequestException.java @@ -0,0 +1,47 @@ +/** + * + */ +package uk.org.ury.backend.server.exceptions; + +/** + * Exception thrown when the server meets a malformed request, or + * part of one. + * + * @author Matt Windsor + */ + +public class BadRequestException extends HandlingException +{ + + /** + * + */ + private static final long serialVersionUID = 1825771401085225357L; + + + /** + * Construct a new BadRequestException with a default reason. + */ + + public + BadRequestException () + { + super ("Bad request."); + } + + + /** + * Construct a new HandlerNotFoundException with a chained + * exception. + * + * @param cause The exception that this new exception is to + * wrap. + */ + + public + BadRequestException (Throwable cause) + { + super ("Bad request. (" + + cause.getMessage () + ")", cause); + } +} diff --git a/src/uk/org/ury/backend/server/exceptions/HandleFailureException.java b/src/uk/org/ury/backend/server/exceptions/HandleFailureException.java new file mode 100644 index 0000000..0efb7a3 --- /dev/null +++ b/src/uk/org/ury/backend/server/exceptions/HandleFailureException.java @@ -0,0 +1,46 @@ +/** + * + */ +package uk.org.ury.backend.server.exceptions; + +/** + * Generic exception thrown when a server request handler fails to + * handle a request. + * + * @author Matt Windsor + */ + +public class HandleFailureException extends HandlingException +{ + + /** + * Change this! ---v + */ + + private static final long serialVersionUID = -397479334359858162L; + + + /** + * Construct a new HandleFailureException with a + * default reason. + */ + + public + HandleFailureException () + { + super ("Server request handler failed to handle the request."); + } + + + /** + * Construct a new HandleFailureException. + * + * @param reason The explanation for the exception. + */ + + public + HandleFailureException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/backend/server/exceptions/HandlerNotFoundException.java b/src/uk/org/ury/backend/server/exceptions/HandlerNotFoundException.java new file mode 100644 index 0000000..a7ba136 --- /dev/null +++ b/src/uk/org/ury/backend/server/exceptions/HandlerNotFoundException.java @@ -0,0 +1,51 @@ +/** + * + */ +package uk.org.ury.backend.server.exceptions; + +/** + * Exception thrown when the server request handler requested + * by the client is not * found in the class space. + * + * @author Matt Windsor + */ + +public class HandlerNotFoundException extends HandlingException +{ + + /** + * TODO: Change this! ---v + */ + + private static final long serialVersionUID = -397479334359858162L; + + + /** + * Construct a new HandlerNotFoundException with a + * default reason. + */ + + public + HandlerNotFoundException () + { + super ("Handler not found."); + } + + + /** + * Construct a new HandlerNotFoundException with a class name and + * chained exception. + * + * @param className The name of the missing handler class. + * + * @param cause The exception that this new exception is to + * wrap. + */ + + public + HandlerNotFoundException (String className, Throwable cause) + { + super ("Handler " + className + " not found. (" + + cause.getMessage () + ")", cause); + } +} diff --git a/src/uk/org/ury/backend/server/exceptions/HandlerSetupFailureException.java b/src/uk/org/ury/backend/server/exceptions/HandlerSetupFailureException.java new file mode 100644 index 0000000..d70624a --- /dev/null +++ b/src/uk/org/ury/backend/server/exceptions/HandlerSetupFailureException.java @@ -0,0 +1,55 @@ +/** + * + */ +package uk.org.ury.backend.server.exceptions; + +/** + * Exception thrown when the server request handler requested + * by the client cannot be set up to process the request. + * + * @author Matt Windsor + */ + +public class HandlerSetupFailureException extends HandlingException +{ + + /** + * TODO: Change this! ---v + */ + + private static final long serialVersionUID = -397479334359858162L; + + + /** + * Construct a new HandlerNotFoundException with a + * default reason. + */ + + public + HandlerSetupFailureException () + { + super ("Handler setup failed."); + } + + + /** + * Construct a new HandlerSetupFailureException with a class name + * and chained exception. + * + * Use this to hide exception specifics from higher abstraction + * layers. + * + * @param className The name of the failed handler class. + * + * @param cause The exception that this new exception is to + * wrap. + */ + + public + HandlerSetupFailureException (String className, + Throwable cause) + { + super ("Setup for handler " + className + " failed (reason: " + + cause.getMessage () + ").", cause); + } +} diff --git a/src/uk/org/ury/backend/server/exceptions/HandlingException.java b/src/uk/org/ury/backend/server/exceptions/HandlingException.java new file mode 100644 index 0000000..edca0aa --- /dev/null +++ b/src/uk/org/ury/backend/server/exceptions/HandlingException.java @@ -0,0 +1,48 @@ +/** + * + */ +package uk.org.ury.backend.server.exceptions; + +/** + * Generic exception thrown when the server cannot handle a request. + * + * @author Matt Windsor + */ + +public class HandlingException extends Exception +{ + /** + * TODO: Change this! ---v + */ + + private static final long serialVersionUID = -397479334359858162L; + + + /** + * Construct a HandlingException with a reason. + * + * @param string The reason to present. + */ + + public + HandlingException (String string) + { + super (string); + } + + + /** + * Construct a HandlingException with a reason and a cause to + * chain. + * + * @param string The reason to present. + * + * @param cause The thrown cause that this exception should wrap. + */ + + public + HandlingException (String string, Throwable cause) + { + super (string, cause); + } +} diff --git a/src/uk/org/ury/backend/server/exceptions/NotAHandlerException.java b/src/uk/org/ury/backend/server/exceptions/NotAHandlerException.java new file mode 100644 index 0000000..d5c9d93 --- /dev/null +++ b/src/uk/org/ury/backend/server/exceptions/NotAHandlerException.java @@ -0,0 +1,34 @@ +/** + * + */ +package uk.org.ury.backend.server.exceptions; + +/** + * Exception thrown if the class requested as a handler by the client + * is, in fact, not a handler (it does not implement RequestHandler). + * + * @author Matt Windsor + */ + +public class NotAHandlerException extends HandlingException +{ + /** + * + */ + private static final long serialVersionUID = -7268289187311868036L; + + /** + * Construct a NotAHandlerException with the name of the class that + * is not a handler. + * + * @param className The name of the offending class. + */ + + public + NotAHandlerException (String className) + { + super ("Class " + className + " is not a request handler."); + // TODO Auto-generated constructor stub + } + +} diff --git a/src/uk/org/ury/backend/server/exceptions/UnknownFunctionException.java b/src/uk/org/ury/backend/server/exceptions/UnknownFunctionException.java new file mode 100644 index 0000000..c0a8a06 --- /dev/null +++ b/src/uk/org/ury/backend/server/exceptions/UnknownFunctionException.java @@ -0,0 +1,37 @@ +/* + * UnknownFunctionException.java + * ----------------------------- + * + * Part of the URY Server Platform + * + * V0.00 2011/03/20 + * + * (C) 2011 URY Computing + */ + +package uk.org.ury.backend.server.exceptions; + +/** + * Exception thrown when a handler receives a request for a path that does not + * correspond to one of its functions. + * + * @author Matt Windsor + * + */ +public class UnknownFunctionException extends HandlingException { + /** + * + */ + private static final long serialVersionUID = -7557785978712465975L; + + /** + * Construct a new UnknownFunctionException. + * + * @param path + * The path that was requested. + */ + public UnknownFunctionException(String path) { + super("Not found: " + path); + } + +} diff --git a/src/uk/org/ury/backend/server/package.html b/src/uk/org/ury/backend/server/package.html new file mode 100644 index 0000000..b19b3b7 --- /dev/null +++ b/src/uk/org/ury/backend/server/package.html @@ -0,0 +1,23 @@ + + + + + uk.org.ury.server + + +

The URY Server kernel.

+

The URY Server provides high-level access to the assets + (database, files and sound playback) available on the computer + systems of a radio station, exposing an intuitive application + programming interface using standard formats.

+

The server kernel consists of an Apache HttpCore-based HTTP + server implementation, which serves the high-level interface + to the server, code for managing the external modules that make + up the URY backend, and a common provider of objects useful to + server-level modules.

+

Though designed and built to replace the systems in place + at University Radio York, it is hoped that the URY Server will + be of use + + \ No newline at end of file diff --git a/src/uk/org/ury/client/Client.java b/src/uk/org/ury/client/Client.java deleted file mode 100644 index 5b8be62..0000000 --- a/src/uk/org/ury/client/Client.java +++ /dev/null @@ -1,60 +0,0 @@ -package uk.org.ury.client; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; - -import java.util.Map; - -import uk.org.ury.protocol.ProtocolUtils; -import uk.org.ury.protocol.exceptions.DecodeFailureException; - -public class Client { - /** - * Get a raw response from the server. - * - * @param file - * The "file", including path and query string, to fetch from the - * server. - * - * @return The response from the server, as a key-value map. - * - * @throws DecodeFailureException - * if the decode failed. - */ - public Map get(String file) throws DecodeFailureException { - URL url = null; - URLConnection uc = null; - String result = ""; - - try { - url = new URL("http://localhost:8000" + file); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - uc = url.openConnection(); - - BufferedReader in = new BufferedReader(new InputStreamReader( - uc.getInputStream())); - - String inputLine; - - for (inputLine = in.readLine(); inputLine != null; inputLine = in - .readLine()) { - result += inputLine; - } - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - return ProtocolUtils.decode(result); - } -} diff --git a/src/uk/org/ury/client/test/ClientTest.java b/src/uk/org/ury/client/test/ClientTest.java deleted file mode 100644 index 3ee70ef..0000000 --- a/src/uk/org/ury/client/test/ClientTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * - */ -package uk.org.ury.client.test; - -import java.util.Map; - -import junit.framework.Assert; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import uk.org.ury.client.Client; -import uk.org.ury.protocol.Directive; -import uk.org.ury.protocol.exceptions.DecodeFailureException; - -/** - * JUnit test for the low-level client logic. - * - * @author Matt Windsor - */ -public class ClientTest { - - /** - * @throws java.lang.Exception - */ - @Before - public void setUp() throws Exception { - } - - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - } - - /** - * Test method for {@link uk.org.ury.client.Client#get(java.lang.String)}. - */ - @Test - public void testGet() { - Client client = new Client(); - - Map response = null; - - try { - response = client.get("/server/ServerRequestHandler?function=test"); - } catch (DecodeFailureException e) { - e.printStackTrace(); - } - - Assert.assertEquals("Test succeeded.", - response.get(Directive.INFO.toString())); - } -} diff --git a/src/uk/org/ury/common/library/LibraryUtils.java b/src/uk/org/ury/common/library/LibraryUtils.java new file mode 100644 index 0000000..335b8b5 --- /dev/null +++ b/src/uk/org/ury/common/library/LibraryUtils.java @@ -0,0 +1,144 @@ +/** + * + */ +package uk.org.ury.common.library; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import uk.org.ury.backend.database.DatabaseDriver; +import uk.org.ury.backend.database.exceptions.QueryFailureException; +import uk.org.ury.common.library.exceptions.EmptySearchException; +import uk.org.ury.common.library.item.LibraryItem; +import uk.org.ury.common.library.item.LibraryItemProperty; + + + +/** + * A set of common utility routines to facilitate the extraction of + * library items from the library areas of the URY database. + * + * @author Matt Windsor + */ + +public class LibraryUtils +{ + /** + * Perform a library search. + * + * @param db The database to query. + * + * @param search The search fragment to include in the search. + * Can be empty or null. + * + * @throws IllegalArgumentException if the search term is + * are null. + * + * @throws QueryFailureException if the database backend + * yielded an error while executing the search + * query. + * + * @throws EmptySearchException if the search term is + * empty (to be handled as a user error). + * + * @return a list of LibraryItems matching the search terms. + */ + + public static List + search (DatabaseDriver db, String search) + throws QueryFailureException, EmptySearchException + { + if (db == null) + throw new IllegalArgumentException ("Database handle is null."); + + if (search == null) + throw new IllegalArgumentException ("Search string is null."); + + List results = new ArrayList (); + + + // Return empty set if the search term is null. + + if (search.equals("")) + throw new EmptySearchException (); + + + ResultSet rs = null; + + Object[] params = {"%" + search + "%", "%" + search + "%", "%" + search + "%"}; + + try + { + rs = db.executeQuery ( + "SELECT r.title AS album, t.title," + + " t.artist, recordlabel AS label, status, media AS medium, format," + + " datereleased, EXTRACT(EPOCH FROM dateadded) as dateadded," + + " EXTRACT(EPOCH FROM datetime_lastedit) AS dateedited," + + " shelfletter, shelfnumber, cdid, digitised, clean" + + " FROM rec_record AS r" + + " INNER JOIN rec_track AS t ON (r.recordid = t.recordid)" + + " WHERE t.title ILIKE ?" + + " OR t.artist ILIKE ?" + + " OR r.title ILIKE ?" + + " ORDER BY digitised DESC, medium ASC, r.title ASC," + + " t.artist ASC, t.title ASC;", params, 50); + } + catch (SQLException e) + { + throw new QueryFailureException (e.getMessage ()); + } + + try + { + while (rs.next ()) + { + results.add (translateRow (rs)); + } + } + catch (SQLException e) + { + throw new QueryFailureException (e.getMessage ()); + } + + return results; + } + + + /** + * Translate a row retrieved from the database into a LibraryItem. + * + * @param rs The result-set, or database cursor, pointing to the + * row to translate. + * + * @return A new LibraryItem containing the properties extracted + * from the translated row. + */ + + private static LibraryItem + translateRow (ResultSet rs) + { + // Translate SQL columns into a list of properties. + + Map properties = new HashMap (); + + for (LibraryItemProperty p : LibraryItemProperty.values ()) + { + try + { + properties.put (p, rs.getString (p.sql)); + } + catch (SQLException e) + { + // Ignore this, as it is almost certainly just a non-existent + // property. + } + } + + + return new LibraryItem (properties); + } +} diff --git a/src/uk/org/ury/common/library/exceptions/EmptySearchException.java b/src/uk/org/ury/common/library/exceptions/EmptySearchException.java new file mode 100644 index 0000000..fc4ed68 --- /dev/null +++ b/src/uk/org/ury/common/library/exceptions/EmptySearchException.java @@ -0,0 +1,49 @@ +/** + * + */ +package uk.org.ury.common.library.exceptions; + +/** + * Exception thrown when a library search is initiated + * in which the query string is null. + * + * Frontends should handle this nicely. Do NOT treat this + * as a fatal error! + * + * @author Matt Windsor + */ + +public class EmptySearchException extends Exception +{ + + /** + * Change this! ---v + */ + + private static final long serialVersionUID = -397479334359858162L; + + + /** + * Construct a new EmptySearchException with a + * default reason. + */ + + public + EmptySearchException () + { + super ("Query string was empty."); + } + + + /** + * Construct a new EmptySearchException. + * + * @param reason The explanation for the exception. + */ + + public + EmptySearchException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/common/library/item/LibraryItem.java b/src/uk/org/ury/common/library/item/LibraryItem.java new file mode 100644 index 0000000..3e81041 --- /dev/null +++ b/src/uk/org/ury/common/library/item/LibraryItem.java @@ -0,0 +1,26 @@ +/** + * + */ +package uk.org.ury.common.library.item; + + +import java.util.Map; + +import uk.org.ury.backend.database.DatabaseItem; + + +/** + * An item in the URY library. + * + * @author Matt Windsor + */ + +public class LibraryItem extends DatabaseItem +{ + public + LibraryItem (Map properties) + { + super (properties); + } +} \ No newline at end of file diff --git a/src/uk/org/ury/common/library/item/LibraryItemProperty.java b/src/uk/org/ury/common/library/item/LibraryItemProperty.java new file mode 100644 index 0000000..2e4d2ef --- /dev/null +++ b/src/uk/org/ury/common/library/item/LibraryItemProperty.java @@ -0,0 +1,62 @@ +package uk.org.ury.common.library.item; + +/** + * The parameters that are stored in the LibraryItem. + * + * @author Matt Windsor + */ + +public enum LibraryItemProperty + { + // Constant SQL identifier + TITLE ("title"), + ALBUM ("album"), + ARTIST ("artist"), + LABEL ("label"), + STATUS ("status"), + MEDIUM ("medium"), + FORMAT ("format"), + DATE_RELEASED ("datereleased"), + DATE_ADDED ("dateadded"), + DATE_EDITED ("dateedited"), + SHELF_LETTER ("shelfletter"), + SHELF_NUMBER ("shelfnumber"), + CD_ID ("cdid"), + IS_DIGITISED ("digitised"), + IS_CLEAN ("clean"); + + + public final String sql; + + + private + LibraryItemProperty (String sql) + { + this.sql = sql; + } + + + /** + * Retrieve a LibraryItemProperty given its SQL identifier. + * + * @param string The SQL identifier. + * @return The first property to match. + * + * @throws IllegalArgumentException if no matches were + * found. + */ + + public static LibraryItemProperty + getFromSQL (String string) + { + // TODO: Better exception? + + for (LibraryItemProperty prop : values ()) + { + if (prop.sql.equals (string)) + return prop; + } + + throw new IllegalArgumentException ("Nonexistent property SQL."); + } + }; \ No newline at end of file diff --git a/src/uk/org/ury/common/protocol/Directive.java b/src/uk/org/ury/common/protocol/Directive.java new file mode 100644 index 0000000..e1edd98 --- /dev/null +++ b/src/uk/org/ury/common/protocol/Directive.java @@ -0,0 +1,18 @@ +/** + * + */ +package uk.org.ury.common.protocol; + +/** + * Directives supported by the protocol. + * + * @author Matt Windsor + */ + +public enum Directive + { + INFO, // Information string (can usually be ignored) + ITEMS, // Item + STATUS, // Status code (from the enum Status) + REASON; // Error reason + } diff --git a/src/uk/org/ury/common/protocol/ProtocolUtils.java b/src/uk/org/ury/common/protocol/ProtocolUtils.java new file mode 100644 index 0000000..8686eb7 --- /dev/null +++ b/src/uk/org/ury/common/protocol/ProtocolUtils.java @@ -0,0 +1,85 @@ +/** + * + */ +package uk.org.ury.common.protocol; + +import java.util.Map; + +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +import uk.org.ury.common.protocol.exceptions.DecodeFailureException; +import uk.org.ury.common.protocol.exceptions.InvalidMessageException; + +/** + * Utilities for converting between strings encoded in the response protocol and + * collections of items, as well as validating and unpicking protocol messages. + * + * @author Matt Windsor + * + */ + +public class ProtocolUtils { + /** + * Encode a key-value map into a protocol string. + * + * The map can contain strings, lists and other maps. Other types may be + * accepted by the underlying encoding engine, but the above types are the + * only ones explicitly accepted. + * + * @param items + * The key-value map of items, which should contain strings, + * lists and maps. The keys of any map should be protocol + * directives. + * + * @return A string containing the encoded representation of the map. + */ + public static String encode(Map items) { + return JSONValue.toJSONString(items); + } + + /** + * Decode a protocol string into a key-value map. + * + * @param string + * The string to decode. + * + * @return A key-value map mapping directives to strings, lists and maps. + * + * @throws DecodeFailureException + * if the decoding engine returns something other than a map. + */ + public static Map decode(String string) throws DecodeFailureException { + Object result = JSONValue.parse(string); + + if (result instanceof JSONObject) + return (JSONObject) result; + else + throw new DecodeFailureException("Result not a map."); + } + + /** + * Check if a response is flagged as having OK status. + * + * @param response + * The response message, as a key-value map (eg in decoded + * format). + * + * @return true if the response is flagged with OK status, false if not (eg + * ERROR status). + * + * @throws InvalidMessageException + * if the response is invalid (eg the status is missing). + */ + public static boolean responseIsOK(Map response) + throws InvalidMessageException { + if (response.containsKey(Directive.STATUS.toString()) == false) + throw new InvalidMessageException("No status line in response."); + + if ((response.get(Directive.STATUS.toString()) instanceof String) == false) + throw new InvalidMessageException("Status is not a string."); + + return (((String) response.get(Directive.STATUS.toString())) + .equals(Status.OK.toString())); + } +} diff --git a/src/uk/org/ury/common/protocol/Status.java b/src/uk/org/ury/common/protocol/Status.java new file mode 100644 index 0000000..b424fc9 --- /dev/null +++ b/src/uk/org/ury/common/protocol/Status.java @@ -0,0 +1,17 @@ +/** + * + */ +package uk.org.ury.common.protocol; + + +/** + * Statuses that can follow the STATUS directory. + * + * @author Matt Windsor + */ + +public enum Status + { + OK, // The request was processed OK; response should be valid + ERROR // An error occurred; message provided as REASON directive + } diff --git a/src/uk/org/ury/common/protocol/exceptions/DecodeFailureException.java b/src/uk/org/ury/common/protocol/exceptions/DecodeFailureException.java new file mode 100644 index 0000000..978ca7b --- /dev/null +++ b/src/uk/org/ury/common/protocol/exceptions/DecodeFailureException.java @@ -0,0 +1,32 @@ +/** + * + */ +package uk.org.ury.common.protocol.exceptions; + + +/** + * Exception thrown when the protocol decoder fails. + * + * @author Matt Windsor + */ + +public class DecodeFailureException extends Exception +{ + /** + * + */ + private static final long serialVersionUID = -3972492943653273528L; + + + /** + * Construct a new DecodeFailureException with a reason. + * + * @param reason The reason for throwing the exception. + */ + + public + DecodeFailureException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/common/protocol/exceptions/InvalidMessageException.java b/src/uk/org/ury/common/protocol/exceptions/InvalidMessageException.java new file mode 100644 index 0000000..739b501 --- /dev/null +++ b/src/uk/org/ury/common/protocol/exceptions/InvalidMessageException.java @@ -0,0 +1,33 @@ +/** + * + */ +package uk.org.ury.common.protocol.exceptions; + + +/** + * Generic exception thrown when a protocol function cannot process a + * message due to an issue with the message. + * + * @author Matt Windsor + */ + +public class InvalidMessageException extends Exception +{ + /** + * + */ + private static final long serialVersionUID = -3972492943653273528L; + + + /** + * Construct a new InvalidMessageException with a reason. + * + * @param reason The reason for throwing the exception. + */ + + public + InvalidMessageException (String reason) + { + super (reason); + } +} diff --git a/src/uk/org/ury/common/show/ShowChannel.java b/src/uk/org/ury/common/show/ShowChannel.java new file mode 100644 index 0000000..1c1b8ce --- /dev/null +++ b/src/uk/org/ury/common/show/ShowChannel.java @@ -0,0 +1,120 @@ +/* + * ShowChannel.java + * ------------------ + * + * Part of the URY Presentation Suite + * + * V0.00 2011/03/21 + * + * (C) 2011 URY Computing + */ + +package uk.org.ury.common.show; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractListModel; + +import uk.org.ury.common.show.item.ShowItem; + +/** + * A channel of ShowItems in a show. + * + * @author Matt Windsor + */ +public class ShowChannel extends AbstractListModel { + /** + * + */ + private static final long serialVersionUID = -4651104185166068150L; + + /* Items enqueued in channel. */ + private List items; + + /** + * Constructs a new, empty channel. + */ + public ShowChannel() { + items = new ArrayList(); + } + + /** + * Adds a new item to the channel. + * + * @param index + * The position at which to add the item. + * + * @param item + * The new item to add. + * + * @throws IllegalArgumentException + * if the item is null, the index is negative or the index is + * out of bounds. + */ + public void add(int index, ShowItem item) { + if (item == null) + throw new IllegalArgumentException("Item is null."); + + if (index < 0 || index >= items.size()) + throw new IllegalArgumentException("Index " + index + + " out of bounds."); + + items.add(index, item); + fireIntervalAdded(this, index, index); + } + + /** + * Adds a new item to the end of the channel. + * + * @param item + * The new item to add. + */ + public void add(ShowItem item) { + if (item == null) + throw new IllegalArgumentException("Item is null."); + + items.add(item); + fireIntervalAdded(this, items.size() - 1, items.size() - 1); + } + + /** + * Retrieves an item from the channel. + * + * @param index + * The index of the item to retrieve from the channel. + * + * @return the item at the given index in the list. + * + * @throws IllegalArgumentException + * if the index is negative or overflowing. + */ + public ShowItem get(int index) { + if (index < 0 || index >= items.size()) + throw new IllegalArgumentException("Index " + index + + " out of bounds."); + + return items.get(index); + } + + /** + * List model retrieval wrapper for get. + * + * @param index + * The index of the item to retrieve from the channel. + * + * @return the item at the given index in the list. + */ + @Override + public Object getElementAt(int index) { + return get(index); + } + + /** + * @return the size of the list. + */ + @Override + public int getSize() { + return items.size(); + } +} diff --git a/src/uk/org/ury/common/show/ShowUtils.java b/src/uk/org/ury/common/show/ShowUtils.java new file mode 100644 index 0000000..a806d8f --- /dev/null +++ b/src/uk/org/ury/common/show/ShowUtils.java @@ -0,0 +1,190 @@ +/* + * ShowUtils.java + * ------------------ + * + * Part of the URY Backend Platform + * + * V0.00 2011/03/21 + * + * (C) 2011 URY Computing + */ + +package uk.org.ury.common.show; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.ResourceBundle; + +import uk.org.ury.backend.database.DatabaseDriver; +import uk.org.ury.backend.database.exceptions.QueryFailureException; +import uk.org.ury.common.show.item.ShowItem; +import uk.org.ury.common.show.item.ShowItemProperty; + + +/** + * A set of common utility routines to facilitate the extraction of show items + * from the show storage areas of the URY database. + * + * @author Matt Windsor + */ +public class ShowUtils { + /** + * The number of channels reserved for show items. + * + * TODO: move this somewhere more appropriate. + */ + public static final int NUM_CHANNELS = 3; + + // Maximum number of results to pull from database + private static final int MAX_RESULTS = 50; + + // Resource bundle (for exception reasons) + private static ResourceBundle rb = ResourceBundle.getBundle(ShowUtils.class + .getPackage().getName() + ".ShowUtils"); + + /** + * Return the names of the public track folders, or "bins". + * + * @param db + * The database to query. + * + * @return a list of the public folder names. The list may be empty. + * + * @throws IllegalArgumentException + * if the database is null, the show ID is negative or the + * channel index falls out of bounds. + * + * @throws QueryFailureException + * if the database backend yielded an error while executing the + * search query. + */ + public static List getPublicFolders(DatabaseDriver db) + throws QueryFailureException { + if (db == null) + throw new IllegalArgumentException( + rb.getString("ERR_DB_HANDLE_IS_NULL")); + + List results = new ArrayList(); + + ResultSet rs = null; + + try { + rs = db.executeQuery("SELECT share AS name, description" + + " FROM baps_filefolder" + " WHERE baps_filefolder.public" + + " = TRUE" + " ORDER BY filefolderid ASC", MAX_RESULTS); + } catch (SQLException e1) { + throw new QueryFailureException(e1.getMessage()); + } + + try { + while (rs.next()) { + results.add(rs.getString(2)); + } + } catch (SQLException e) { + throw new QueryFailureException(e.getMessage()); + } + + return results; + } + + /** + * Given a show and a channel, retrieve a list of all show items bound to + * that channel for the show. + * + * @param db + * The database to query. + * + * @param showID + * The unique number that identifies the show. + * + * @param channel + * The index of the channel to query. + * + * @return a list of ShowItems extracted from the show and channel. The list + * may be empty. + * + * @throws IllegalArgumentException + * if the database is null, the show ID is negative or the + * channel index falls out of bounds. + * + * @throws QueryFailureException + * if the database backend yielded an error while executing the + * search query. + */ + public static List getChannelList(DatabaseDriver db, int showID, + int channel) throws QueryFailureException { + if (db == null) + throw new IllegalArgumentException( + rb.getString("ERR_DB_HANDLE_IS_NULL")); + + if (showID < 0) + throw new IllegalArgumentException( + rb.getString("ERR_NEGATIVE_SHOW_ID")); + + if (channel < 0 || channel >= NUM_CHANNELS) + throw new IllegalArgumentException( + rb.getString("ERR_CH_OUT_OF_BOUNDS")); + + List results = new ArrayList(); + + ResultSet rs = null; + + Object[] params = { showID, channel }; + + try { + rs = db.executeQuery("SELECT name1, name2, position" + + " FROM baps_show" + " INNER JOIN baps_listing" + + " ON baps_show.showid" + + " = baps_listing.showid" + + " INNER JOIN baps_item" + + " ON baps_listing.listingid" + + " = baps_item.listingid" + + " WHERE baps_show.showid" + + " = ?" + + " AND baps_listing.channel" + + " = ?" + + " ORDER BY position ASC", params, MAX_RESULTS); + } catch (SQLException e) { + throw new QueryFailureException(e.getMessage()); + } + + try { + while (rs.next()) { + results.add(translateRow(rs)); + } + } catch (SQLException e) { + throw new QueryFailureException(e.getMessage()); + } + return results; + } + + /** + * Translate a row retrieved from the database into a ShowItem. + * + * @param rs + * The result-set, or database cursor, pointing to the row to + * translate. + * + * @return A new ShowItem containing the properties extracted from the + * translated row. + */ + private static ShowItem translateRow(ResultSet rs) { + // Translate SQL columns into a list of properties. + + HashMap properties = new HashMap(); + + for (ShowItemProperty p : ShowItemProperty.values()) { + try { + properties.put(p, rs.getString(p.sql)); + } catch (SQLException e) { + // Ignore this, as it is almost certainly just a non-existent + // property. + } + } + + return new ShowItem(properties); + } +} \ No newline at end of file diff --git a/src/uk/org/ury/common/show/ShowUtils.properties b/src/uk/org/ury/common/show/ShowUtils.properties new file mode 100644 index 0000000..7f8a2fa --- /dev/null +++ b/src/uk/org/ury/common/show/ShowUtils.properties @@ -0,0 +1,4 @@ +// Exceptions +ERR_DB_HANDLE_IS_NULL = "Database handle is null." +ERR_NEGATIVE_SHOW_ID = "Show ID is negative." +ERR_CH_OUT_OF_BOUNDS = Channel index is out of bounds." \ No newline at end of file diff --git a/src/uk/org/ury/common/show/item/ShowItem.java b/src/uk/org/ury/common/show/item/ShowItem.java new file mode 100644 index 0000000..d1d1be1 --- /dev/null +++ b/src/uk/org/ury/common/show/item/ShowItem.java @@ -0,0 +1,68 @@ +/** + * + */ +package uk.org.ury.common.show.item; + + +import java.util.Map; + +import uk.org.ury.backend.database.DatabaseItem; +import uk.org.ury.backend.database.exceptions.MissingPropertyException; + + +/** + * An item in the show database. + * + * @author Matt Windsor + */ + +public class ShowItem extends DatabaseItem +{ + /** + * Construct a new ShowItem. + * + * @param properties The map of properties to store in the show item. + */ + + public + ShowItem (Map properties) + { + super (properties); + } + + + /** + * @return a string representation of the ShowItem. + */ + + @Override + public String + toString () + { + String name1; + String name2; + + try + { + name1 = get (ShowItemProperty.NAME1); + } + catch (MissingPropertyException e1) + { + name1 = "Unknown"; + } + + try + { + name2 = get (ShowItemProperty.NAME2); + } + catch (MissingPropertyException e2) + { + name2 = null; + } + + if (name2 != null) + return name1 + " - " + name2; + else + return name1; + } +} diff --git a/src/uk/org/ury/common/show/item/ShowItemProperty.java b/src/uk/org/ury/common/show/item/ShowItemProperty.java new file mode 100644 index 0000000..43d4e99 --- /dev/null +++ b/src/uk/org/ury/common/show/item/ShowItemProperty.java @@ -0,0 +1,26 @@ +package uk.org.ury.common.show.item; + + +/** + * Enumeration of the parameters that are stored in a ShowItem. + * + * @author Matt Windsor + */ + +public enum ShowItemProperty + { + // Constant SQL identifier + NAME1 ("name1"), + NAME2 ("name2"), + POSITION ("positionid"); + + + public final String sql; + + + private + ShowItemProperty (String sql) + { + this.sql = sql; + } + }; \ No newline at end of file diff --git a/src/uk/org/ury/common/show/item/package.html b/src/uk/org/ury/common/show/item/package.html new file mode 100644 index 0000000..0b2af5a --- /dev/null +++ b/src/uk/org/ury/common/show/item/package.html @@ -0,0 +1,14 @@ + + + + + uk.org.ury.show.item + + +

The show item class and related properties.

+

The two contained classes, ShowItem and ShowItemProperty, + implement the storage of items (songs, audio tracks, etc) that + can be placed in show channels.

+ + \ No newline at end of file diff --git a/src/uk/org/ury/config/Auth.java b/src/uk/org/ury/config/Auth.java deleted file mode 100644 index 81be6e2..0000000 --- a/src/uk/org/ury/config/Auth.java +++ /dev/null @@ -1,36 +0,0 @@ -package uk.org.ury.config; - -/** - * A login authorisation configuration - * - * @author Nathan Lasseter - */ -public class Auth { - - private String user; - private String pass; - - /** - * Get the username of the login - * - * @return String username - */ - public String getUser() { return user; } - /** - * Get the password of the login - * - * @return String password - */ - public String getPass() { return pass; } - - /** - * Create a login auth object - * @param user The username for the login - * @param pass The password for the login - */ - public Auth(String user, String pass) { - this.user = user; - this.pass = pass; - } - -} \ No newline at end of file diff --git a/src/uk/org/ury/config/ConfigReader.java b/src/uk/org/ury/config/ConfigReader.java deleted file mode 100644 index ed2d852..0000000 --- a/src/uk/org/ury/config/ConfigReader.java +++ /dev/null @@ -1,115 +0,0 @@ -package uk.org.ury.config; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -import uk.org.ury.database.exceptions.MissingCredentialsException; - -/** - * Reads in an XML config file and creates config objects - * - * @author Nathan Lasseter - */ -public class ConfigReader { - - private Database database = null; - private Auth roAuth = null; - private Auth rwAuth = null; - - /** - * Get the database configuration - * - * @return Database database - */ - public Database getDatabase() { return database; } - /** - * Get the read only login auth configuration - * - * @return Auth roAauth - */ - public Auth getRoAuth() { return roAuth; } - /** - * Get the read write login auth configuration - * - * @return Auth rwAauth - */ - public Auth getRwAuth() { return rwAuth; } - - /** - * Read in the config file and create the Database and Auth configuration objects. - * Specify a config file. - * @throws MissingCredentialsException - */ - public ConfigReader(String configFile) throws MissingCredentialsException { - - try { - DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); - Document doc = docBuilder.parse (configFile); - doc.getDocumentElement().normalize(); - - NodeList nList = doc.getElementsByTagName("auth"); - for(int i = 0; i < nList.getLength(); i++) { - Node nNode = nList.item(i); - if(nNode.getNodeType() == Node.ELEMENT_NODE) { - Element eElement = (Element) nNode; - String user = getTagValue("user", eElement); - String pass = getTagValue("pass", eElement); - String sType = getTagValue("type", eElement); - if(sType.equalsIgnoreCase("read_only")) { - if(roAuth != null) continue; - roAuth = new Auth(user, pass); - } else if(sType.equalsIgnoreCase("read_write")) { - if(rwAuth != null) continue; - rwAuth = new Auth(user, pass); - } else { - throw new IllegalArgumentException("Unused user class."); - } - } - } - - nList = doc.getElementsByTagName("database"); - for(int i = 0; i < nList.getLength(); i++) { - if(database != null) break; - Node nNode = nList.item(i); - if(nNode.getNodeType() == Node.ELEMENT_NODE) { - Element eElement = (Element) nNode; - String host = getTagValue("host", eElement); - String port = getTagValue("port", eElement); - String db = getTagValue("db", eElement); - database = new Database(host, Integer.parseInt(port.trim()), db); - } - } - } - - catch(NullPointerException n) { - throw new MissingCredentialsException("An element node is empty."); - } - - catch (SAXParseException err) { - System.out.println ("** Parsing error" + ", line " + err.getLineNumber () + ", uri " + err.getSystemId ()); - System.out.println(" " + err.getMessage ()); - } - catch (SAXException e) { - Exception x = e.getException (); - ((x == null) ? e : x).printStackTrace (); - } - catch (Throwable t) { - t.printStackTrace (); - } - } - - private static String getTagValue(String sTag, Element eElement){ - NodeList nlList= eElement.getElementsByTagName(sTag).item(0).getChildNodes(); - Node nValue = (Node) nlList.item(0); - - return nValue.getNodeValue(); - } -} diff --git a/src/uk/org/ury/config/Database.java b/src/uk/org/ury/config/Database.java deleted file mode 100644 index 19fe9bc..0000000 --- a/src/uk/org/ury/config/Database.java +++ /dev/null @@ -1,45 +0,0 @@ -package uk.org.ury.config; - -/** - * A Database Server configuration - * - * @author Nathan Lasseter - */ -public class Database { - - private String host; - private int port; - private String db; - - /** - * Get the hostname of the database server - * - * @return String hostname - */ - public String getHost() { return host; } - /** - * Get the port the database server is running on - * - * @return int port - */ - public int getPort() { return port; } - /** - * Get the name of the database - * - * @return String database name - */ - public String getDb() { return db; } - - /** - * Create a database object - * @param host The hostname of the database server - * @param port The port that the database server listens on - * @param db The name of the database on the server - */ - public Database(String host, int port, String db) { - this.host = host; - this.port = port; - this.db = db; - } - -} \ No newline at end of file diff --git a/src/uk/org/ury/database/DatabaseDriver.java b/src/uk/org/ury/database/DatabaseDriver.java deleted file mode 100644 index e48b0a1..0000000 --- a/src/uk/org/ury/database/DatabaseDriver.java +++ /dev/null @@ -1,166 +0,0 @@ -package uk.org.ury.database; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import uk.org.ury.config.ConfigReader; -import uk.org.ury.database.exceptions.ConnectionFailureException; -import uk.org.ury.database.exceptions.MissingCredentialsException; - -/** - * A database connection manager that connects to the URY databases using - * suitably privileged accounts, and handles the processing of SQL queries. - * - * @author Matt Windsor - * @author Nathan Lasseter - * - */ -public class DatabaseDriver { - /* The JDBC path used to connect to the URY database. */ - private String DATABASE_PATH = "jdbc:postgresql://"; - - /* The database connection. */ - private Connection conn; - - /** - * Construct a new DatabaseDriver with the given user class. - * - * @param config - * The config with login details. - * - * @param type - * The user class to log in to the database with. - * - * @throws IllegalArgumentException - * if the user class is not supported (this should not happen). - * - * @throws MissingCredentialsException - * if the user class login credentials could not be loaded. - * - * @throws ConnectionFailureException - * if the database backend failed to connect to the database - * server. - */ - public DatabaseDriver(ConfigReader config, UserClass type) - throws MissingCredentialsException, ConnectionFailureException { - try { - connect(config, type); - } catch (SQLException e) { - throw new ConnectionFailureException(e.getMessage()); - } - - } - - /** - * Connect to the URY database. - * - * @param config - * The config to use for the connection. - * @param type - * The access level of the connection - * - * @throws SQLException - * if the database connection failed. - */ - private void connect(ConfigReader config, UserClass type) - throws SQLException { - if (config == null) - throw new IllegalArgumentException("Supplied null config."); - - if (config.getDatabase().getHost() == null) - throw new IllegalArgumentException("config has no associated host."); - - if (config.getDatabase().getDb() == null) - throw new IllegalArgumentException( - "config has no associated database."); - - DATABASE_PATH = DATABASE_PATH + config.getDatabase().getHost() + "/" - + config.getDatabase().getDb(); - - if (type == UserClass.READ_ONLY) { - if (config.getRoAuth().getUser() == null) - throw new IllegalArgumentException( - "config has no associated username."); - if (config.getRoAuth().getPass() == null) - throw new IllegalArgumentException( - "config has no associated password."); - conn = DriverManager.getConnection(DATABASE_PATH, config - .getRoAuth().getUser(), config.getRoAuth().getPass()); - } else if (type == UserClass.READ_WRITE) { - if (config.getRwAuth().getUser() == null) - throw new IllegalArgumentException( - "config has no associated username."); - if (config.getRwAuth().getPass() == null) - throw new IllegalArgumentException( - "config has no associated password."); - conn = DriverManager.getConnection(DATABASE_PATH, config - .getRwAuth().getUser(), config.getRwAuth().getPass()); - } - } - - /** - * Execute an unprepared SQL statement with no arguments. - * - * @param sql - * The SQL statement to execute. - * @param fetchSize - * The maximum number of query rows to return. - * - * @return the JDBC results set. - * - * @throws SQLException - * if a SQL error occurs. - */ - public ResultSet executeQuery(String sql, int fetchSize) - throws SQLException { - Statement st = conn.createStatement(); - st.setFetchSize(fetchSize); - - return st.executeQuery(sql); - } - - /** - * Perform a SQL statement with arguments. - * - * This accepts an array of parameter objects, which must each either be - * String or Integer objects. The objects will be used sequentially to fill - * in '?' placeholders in the query text. - * - * @param sql - * The SQL statement to execute. - * @param params - * A list of parameter objects. - * @param fetchSize - * The maximum number of query rows to return. - * - * @return the set of results from the query. - * - * @throws IllegalArgumentException - * if any of the parameters is unsupported by the database as a - * statement parameter. - * - * @throws SQLException - * if a SQL error occurs. - */ - public ResultSet executeQuery(String sql, Object[] params, int fetchSize) - throws SQLException { - PreparedStatement st = conn.prepareStatement(sql); - - st.setFetchSize(fetchSize); - - for (int i = 0; i < params.length; i++) - if (params[i] instanceof String) - st.setString(i + 1, (String) params[i]); - else if (params[i] instanceof Integer) - st.setInt(i + 1, (Integer) params[i]); - else - throw new IllegalArgumentException("Unsupported parameter #" - + (i + 1)); - - return st.executeQuery(); - } -} diff --git a/src/uk/org/ury/database/DatabaseItem.java b/src/uk/org/ury/database/DatabaseItem.java deleted file mode 100644 index bac23a5..0000000 --- a/src/uk/org/ury/database/DatabaseItem.java +++ /dev/null @@ -1,80 +0,0 @@ -package uk.org.ury.database; - -import java.util.HashMap; -import java.util.Map; - -import uk.org.ury.database.exceptions.MissingPropertyException; - -/** - * An abstract class presenting a template for objects serving as a data - * structure for collections of properties retrieved from a SQL database. - * - * @param E - * The enumeration type used as the property list. - * - * @param T - * The type of datum stored for each property. - * - * @author Matt Windsor - */ -public abstract class DatabaseItem { - private Map properties; - - /** - * Construct a new item from an existing list of properties. - * - * @param properties - * The map of properties that the new item will inherit. - */ - public DatabaseItem(Map properties) { - this.properties = properties; - } - - /** - * Check whether a property has been set in the item. - * - * @return true if the property has been set; false otherwise. - */ - - public boolean has(E property) { - return properties.containsKey(property); - } - - /** - * Query this item for a property. - * - * @param property - * The property to query. - * - * @return The property, if it exists. - * - * @throws MissingPropertyException - * if the property does not exist. - */ - public T get(E property) throws MissingPropertyException { - if (properties.containsKey(property)) - return properties.get(property); - else - throw new MissingPropertyException(property.toString()); - } - - /** - * Retrieve a map of string representations of the properties. - * - * This relies on E and T having meaningful toString methods. - * - * @return a list of lines representing the response. - */ - public Map asResponse() { - // TODO: Fan out implementation details into separate class - Map response = new HashMap(); - - for (E property : properties.keySet()) { - if (properties.get(property) != null) - response.put(property.toString(), properties.get(property) - .toString()); - } - - return response; - } -} diff --git a/src/uk/org/ury/database/UserClass.java b/src/uk/org/ury/database/UserClass.java deleted file mode 100644 index 48cb2d3..0000000 --- a/src/uk/org/ury/database/UserClass.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * - */ -package uk.org.ury.database; - -/** - * The various user classes of the database driver. - * - * These refer to various users in the database proper, and thus grant various - * levels of permission to the program. - * - * Please use the least privileged user class that works. For most cases, - * READ_ONLY should work perfectly. - * - * @author Matt Windsor - * - */ - -public enum UserClass { - // Constant configName - READ_ONLY ("read_only"), - READ_WRITE ("read_write"); - - /** - * The name of the tag in the configuration file that contains the - * credentials for this user class. - */ - public String configName; - - /** - * Constructs a new UserClass. - * - * @param configName The name of the user class in the config. - */ - private UserClass(String configName) { - this.configName = configName; - } -} diff --git a/src/uk/org/ury/database/exceptions/ConnectionFailureException.java b/src/uk/org/ury/database/exceptions/ConnectionFailureException.java deleted file mode 100644 index 030b24f..0000000 --- a/src/uk/org/ury/database/exceptions/ConnectionFailureException.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * - */ -package uk.org.ury.database.exceptions; - -/** - * Exception thrown when the database backend fails to connect to - * the database server, in absence of a more specific exception. - * - * @author Matt Windsor - */ - -public class ConnectionFailureException extends Exception -{ - /** - * - */ - private static final long serialVersionUID = -7353531873142099828L; - - -/** - * Construct a new ConnectionFailureException with a - * default reason. - */ - - public - ConnectionFailureException () - { - super ("Connection failure."); - } - - - /** - * Construct a new ConnectionFailureException. - * - * @param reason The explanation for the exception. - */ - - public - ConnectionFailureException (String reason) - { - super (reason); - } -} diff --git a/src/uk/org/ury/database/exceptions/MissingCredentialsException.java b/src/uk/org/ury/database/exceptions/MissingCredentialsException.java deleted file mode 100644 index 2e45526..0000000 --- a/src/uk/org/ury/database/exceptions/MissingCredentialsException.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * - */ -package uk.org.ury.database.exceptions; - -/** - * Exception thrown when the database credentials required to - * log into the URY database under a user class are missing, - * and thus the log-in cannot continue. - * - * The best practice for handling a MissingCredentialsException - * is to attempt to log into the database with a less privileged - * user class or, if the credentials for read-only access are - * missing, give up. - * - * @author Matt Windsor - */ - -public class MissingCredentialsException extends Exception -{ - - /** - * - */ - - private static final long serialVersionUID = -397479334359858162L; - - - /** - * Construct a new MissingCredentialsException with a - * default reason. - */ - - public - MissingCredentialsException () - { - super ("Missing credentials."); - } - - - /** - * Construct a new MissingCredentialsException. - * - * @param reason The explanation for the exception. - */ - - public - MissingCredentialsException (String reason) - { - super (reason); - } -} diff --git a/src/uk/org/ury/database/exceptions/MissingPropertyException.java b/src/uk/org/ury/database/exceptions/MissingPropertyException.java deleted file mode 100644 index 3766fcf..0000000 --- a/src/uk/org/ury/database/exceptions/MissingPropertyException.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * - */ -package uk.org.ury.database.exceptions; - - -/** - * Exception thrown when a DatabaseItem is queried for a property - * that does not exist. - * - * This is (usually) not a fatal error. - * - * @author Matt Windsor - */ - -public class MissingPropertyException extends Exception -{ - /** - * - */ - private static final long serialVersionUID = -7353531873142099828L; - - -/** - * Construct a new MissingPropertyException with a - * default reason. - */ - - public - MissingPropertyException () - { - super ("Query failure."); - } - - - /** - * Construct a new MissingPropertyException. - * - * @param reason The explanation for the exception. - */ - - public - MissingPropertyException (String reason) - { - super (reason); - } -} diff --git a/src/uk/org/ury/database/exceptions/QueryFailureException.java b/src/uk/org/ury/database/exceptions/QueryFailureException.java deleted file mode 100644 index 5293a7c..0000000 --- a/src/uk/org/ury/database/exceptions/QueryFailureException.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * - */ -package uk.org.ury.database.exceptions; - - -/** - * Exception thrown when the database backend fails to execute - * a query. - * - * @author Matt Windsor - */ - -public class QueryFailureException extends Exception -{ - /** - * - */ - private static final long serialVersionUID = -7353531873142099828L; - - -/** - * Construct a new QueryFailureException with a - * default reason. - */ - - public - QueryFailureException () - { - super ("Query failure."); - } - - - /** - * Construct a new QueryFailureException. - * - * @param reason The explanation for the exception. - */ - - public - QueryFailureException (String reason) - { - super (reason); - } -} diff --git a/src/uk/org/ury/database/exceptions/package.html b/src/uk/org/ury/database/exceptions/package.html deleted file mode 100644 index b5e7cac..0000000 --- a/src/uk/org/ury/database/exceptions/package.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - uk.org.ury.database.exceptions - - -

Exceptions thrown by the database services classes.

- - \ No newline at end of file diff --git a/src/uk/org/ury/database/package.html b/src/uk/org/ury/database/package.html deleted file mode 100644 index 2b138b8..0000000 --- a/src/uk/org/ury/database/package.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - uk.org.ury.database - - -

Database services for the URY Presenter Suite.

-

The classes provided within this package are expected to be - used by the back-end through utility classes, and not - by the frontend, which should use the server API to indirectly - query the database.

- - \ No newline at end of file diff --git a/src/uk/org/ury/frontend/FrontendApplet.java b/src/uk/org/ury/frontend/FrontendApplet.java index 8e0e229..375a260 100644 --- a/src/uk/org/ury/frontend/FrontendApplet.java +++ b/src/uk/org/ury/frontend/FrontendApplet.java @@ -11,7 +11,7 @@ import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import uk.org.ury.frontend.exceptions.LoadFailureException; -import uk.org.ury.testrig.Launcher; +import uk.org.ury.frontend.testrig.Launcher; /** * A frame that hosts a FrontendModulePanel, used for serving frontend panels in diff --git a/src/uk/org/ury/frontend/FrontendFrame.java b/src/uk/org/ury/frontend/FrontendFrame.java index 1683c9b..24a12cb 100644 --- a/src/uk/org/ury/frontend/FrontendFrame.java +++ b/src/uk/org/ury/frontend/FrontendFrame.java @@ -14,330 +14,270 @@ import javax.swing.UnsupportedLookAndFeelException; import uk.org.ury.frontend.exceptions.LoadFailureException; /** - * A frame that hosts a FrontendModulePanel, used for serving frontend - * panels in a window (application mode). + * A frame that hosts a FrontendModulePanel, used for serving frontend panels in + * a window (application mode). * * @author Matt Windsor - * + * */ -public class FrontendFrame extends JFrame implements FrontendMaster -{ - /** - * - */ - - private static final long serialVersionUID = 740928181256928433L; - - private FrontendBanner banner; - private FrontendModulePanel child; - private FrontendControlPanel cpanel; - - - /** - * Construct a new FrontendFrame given an initial frontend module. - * - * Loading will fail with a fatal error if the class is not found, - * or is not an implementor of FrontendModule. - * - * @param moduleName The fully qualified class-name of the module, - * minus the leading "uk.org.ury." domain. - */ - - public - FrontendFrame (String moduleName) - { - super ("URY newBAPS"); - try - { - loadModule (moduleName); - } - catch (LoadFailureException e) - { - fatalError (e.getMessage ()); - } - } - - - /** - * Set up the user interface of the frame. - */ - - public void - setupUI () - { - try - { - // Set System L&F - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName ()); - } - catch (UnsupportedLookAndFeelException e) - { - // handle exception - } - catch (ClassNotFoundException e) - { - // handle exception - } - catch (InstantiationException e) - { - // handle exception - } - catch (IllegalAccessException e) - { - // handle exception - } - - setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); - - Container cp = getContentPane (); - - // Banner - System.out.println (child); - banner = new FrontendBanner (child.getModuleName ()); - - // Composition - - cp.add (banner, BorderLayout.NORTH); - cp.add (child, BorderLayout.CENTER); - - setPreferredSize (new Dimension (800, 600)); - setMinimumSize (new Dimension (800, 600)); - - pack (); - setVisible (true); - } - - - /** - * Load a module into the frontend frame. - * - * Loading will fail with a fatal error if the class is not found, - * or is not an implementor of FrontendModule. - * - * @param moduleName The fully qualified class-name of the module, - * minus the leading "uk.org.ury." domain. - * - * @throws LoadFailureException if the class is - * not found, or is not an implementor of - * FrontendModule. - */ - - @Override - public void - loadModule (String moduleName) - throws LoadFailureException - { - Class moduleClass = null; - - try - { - moduleClass = Class.forName ("uk.org.ury." + moduleName); - } - catch (ClassNotFoundException e) - { - throw new LoadFailureException ("Could not load module: " - + e.getMessage ()); - } - - - if (FrontendModule.class.isAssignableFrom (moduleClass) == false) - { - throw new LoadFailureException ("Could not load module: Not a FrontendModule"); - } - else - { - FrontendModulePanel temp = child; - - try - { - child = ((FrontendModule) moduleClass.newInstance ()).runFrontend (this); - } - catch (InstantiationException e) - { - throw new LoadFailureException ("Could not load module: " - + e.getMessage ()); - } - catch (IllegalAccessException e) - { - throw new LoadFailureException ("Could not load module: " - + e.getMessage ()); - } - - if (temp != null) - remove (temp); - - add (child); - child.setMaster (this); - - if (banner != null) - banner.setTitle (child.getModuleName ()); - - pack (); - } - } - - - /** - * Load a module into the frontend frame, additionally installing - * a control panel to communicate with the previous module. - * - * Loading will fail with a fatal error if the class is not found, - * or is not an implementor of FrontendModule. - * - * @param moduleName The fully qualified class-name of the module, - * minus the leading "uk.org.ury." domain. - * - * @param cPanelName The fully qualified class-name of the control - * panel to install, minus the leading - * "uk.org.ury." domain. - * - * @throws LoadFailureException if the class is - * not found, or is not an implementor of - * FrontendModule. - */ - - @Override - public void - loadModule (String moduleName, String cPanelName) - throws LoadFailureException - { - FrontendModulePanel newParent = child; - loadModule (moduleName); - FrontendModulePanel newChild = child; - - loadControlPanel (cPanelName, newParent, newChild); - } - - - /** - * Load and install a control panel class given the class name. - * - * @param cPanelName The fully qualified class-name of the control - * panel to load, minus the leading - * "uk.org.ury." domain. - * - * @param parent The parent panel in the relationship modelled - * by the control panel interface. - * - * @param child The child panel in the relationship modelled - * by the control panel interface. - * - * @throws LoadFailureException if the class is - * not found, or is not an implementor of - * FrontendControlPanel. - */ - - private void - loadControlPanel (String cPanelName, FrontendModulePanel parent, - FrontendModulePanel child) - throws LoadFailureException - { - Class cPanelClass = null; - - try - { - cPanelClass = Class.forName ("uk.org.ury." + cPanelName); - } - catch (ClassNotFoundException e) - { - throw new LoadFailureException ("Could not load control panel: " - + e.getMessage ()); - } - - - if (FrontendControlPanel.class.isAssignableFrom (cPanelClass)) - { - FrontendControlPanel temp = cpanel; - - try - { - cpanel = ((FrontendControlPanel) cPanelClass.newInstance ()); - } - catch (InstantiationException e) - { - throw new LoadFailureException ("Could not load control panel: " - + e.getMessage ()); - } - catch (IllegalAccessException e) - { - throw new LoadFailureException ("Could not load control panel: " - + e.getMessage ()); - } - - if (temp != null) - remove (temp); - - cpanel.setPanels (parent, child); - cpanel.setMaster (this); - cpanel.setPreviousCPanel (temp); - - add (cpanel, BorderLayout.SOUTH); - pack (); - } - } - - - /** - * Restore an existing module and control panel into the frontend - * frame. - * - * @param mpanel The module panel to restore. - * - * @param cpanel The control panel to restore, if any. A null - * value signifies a lack of control panel. - * - * @throws IllegalArgumentException if the mpanel is null. - */ - - @Override - public void - restoreModule (FrontendModulePanel mpanel, - FrontendControlPanel cpanel) - { - if (mpanel == null) - throw new IllegalArgumentException ("mpanel is null."); - - remove (child); - remove (this.cpanel); - - child = mpanel; - add (child); - banner.setTitle (child.getModuleName ()); - - if (cpanel != null) - add (cpanel, BorderLayout.SOUTH); - - this.cpanel = cpanel; - - pack (); - repaint (); - } - - - /** - * Report a fatal error, - * - * @param message The message, eg the exception message, to report - * to the user. - */ - - @Override - public void - fatalError (String message) - { - FrontendError.reportFatal (message, this); - } - - - /** - * @return the resource directory. - */ - - @Override - public String - getResourceDirectory () - { - return "res/"; - } +public class FrontendFrame extends JFrame implements FrontendMaster { + /** + * + */ + private static final long serialVersionUID = 740928181256928433L; + + private FrontendBanner banner; + private FrontendModulePanel child; + private FrontendControlPanel cpanel; + + /** + * Construct a new FrontendFrame given an initial frontend module. + * + * Loading will fail with a fatal error if the class is not found, or is not + * an implementor of FrontendModule. + * + * @param moduleName + * The fully qualified class-name of the module, minus the + * leading "uk.org.ury." domain. + */ + public FrontendFrame(String moduleName) { + super("URY newBAPS"); + try { + loadModule(moduleName); + } catch (LoadFailureException e) { + fatalError(e.getMessage()); + } + } + + /** + * Set up the user interface of the frame. + */ + public void setupUI() { + try { + // Set System L&F + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException e) { + // handle exception + } catch (ClassNotFoundException e) { + // handle exception + } catch (InstantiationException e) { + // handle exception + } catch (IllegalAccessException e) { + // handle exception + } + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + Container cp = getContentPane(); + + // Banner + System.out.println(child); + banner = new FrontendBanner(child.getModuleName()); + + // Composition + cp.add(banner, BorderLayout.NORTH); + cp.add(child, BorderLayout.CENTER); + + setPreferredSize(new Dimension(800, 600)); + setMinimumSize(new Dimension(800, 600)); + + pack(); + setVisible(true); + } + + /** + * Load a module into the frontend frame. + * + * Loading will fail with a fatal error if the class is not found, or is not + * an implementor of FrontendModule. + * + * @param moduleName + * The fully qualified class-name of the module, minus the + * leading "uk.org.ury." domain. + * + * @throws LoadFailureException + * if the class is not found, or is not an implementor of + * FrontendModule. + */ + @Override + public void loadModule(String moduleName) throws LoadFailureException { + Class moduleClass = null; + + try { + moduleClass = Class.forName("uk.org.ury.frontend.modules." + + moduleName); + } catch (ClassNotFoundException e) { + throw new LoadFailureException("Could not load module: " + + e.getMessage()); + } + + if (FrontendModule.class.isAssignableFrom(moduleClass) == false) { + throw new LoadFailureException( + "Could not load module: Not a FrontendModule"); + } else { + FrontendModulePanel temp = child; + + try { + child = ((FrontendModule) moduleClass.newInstance()) + .runFrontend(this); + } catch (InstantiationException e) { + throw new LoadFailureException("Could not load module: " + + e.getMessage()); + } catch (IllegalAccessException e) { + throw new LoadFailureException("Could not load module: " + + e.getMessage()); + } + + if (temp != null) + remove(temp); + + add(child); + child.setMaster(this); + + if (banner != null) + banner.setTitle(child.getModuleName()); + + pack(); + } + } + + /** + * Load a module into the frontend frame, additionally installing a control + * panel to communicate with the previous module. + * + * Loading will fail with a fatal error if the class is not found, or is not + * an implementor of FrontendModule. + * + * @param moduleName + * The fully qualified class-name of the module, minus the + * leading "uk.org.ury." domain. + * + * @param cPanelName + * The fully qualified class-name of the control panel to + * install, minus the leading "uk.org.ury." domain. + * + * @throws LoadFailureException + * if the class is not found, or is not an implementor of + * FrontendModule. + */ + @Override + public void loadModule(String moduleName, String cPanelName) + throws LoadFailureException { + FrontendModulePanel newParent = child; + loadModule(moduleName); + FrontendModulePanel newChild = child; + + loadControlPanel(cPanelName, newParent, newChild); + } + + /** + * Load and install a control panel class given the class name. + * + * @param cPanelName + * The fully qualified class-name of the control panel to load, + * minus the leading "uk.org.ury." domain. + * + * @param parent + * The parent panel in the relationship modelled by the control + * panel interface. + * + * @param child + * The child panel in the relationship modelled by the control + * panel interface. + * + * @throws LoadFailureException + * if the class is not found, or is not an implementor of + * FrontendControlPanel. + */ + private void loadControlPanel(String cPanelName, + FrontendModulePanel parent, FrontendModulePanel child) + throws LoadFailureException { + Class cPanelClass = null; + + try { + cPanelClass = Class.forName("uk.org.ury.frontend.cpanels." + cPanelName); + } catch (ClassNotFoundException e) { + throw new LoadFailureException("Could not load control panel: " + + e.getMessage()); + } + + if (FrontendControlPanel.class.isAssignableFrom(cPanelClass)) { + FrontendControlPanel temp = cpanel; + + try { + cpanel = ((FrontendControlPanel) cPanelClass.newInstance()); + } catch (InstantiationException e) { + throw new LoadFailureException("Could not load control panel: " + + e.getMessage()); + } catch (IllegalAccessException e) { + throw new LoadFailureException("Could not load control panel: " + + e.getMessage()); + } + + if (temp != null) + remove(temp); + + cpanel.setPanels(parent, child); + cpanel.setMaster(this); + cpanel.setPreviousCPanel(temp); + + add(cpanel, BorderLayout.SOUTH); + pack(); + } + } + + /** + * Restore an existing module and control panel into the frontend frame. + * + * @param mpanel + * The module panel to restore. + * + * @param cpanel + * The control panel to restore, if any. A null value signifies a + * lack of control panel. + * + * @throws IllegalArgumentException + * if the mpanel is null. + */ + @Override + public void restoreModule(FrontendModulePanel mpanel, + FrontendControlPanel cpanel) { + if (mpanel == null) + throw new IllegalArgumentException("mpanel is null."); + + remove(child); + remove(this.cpanel); + + child = mpanel; + add(child); + banner.setTitle(child.getModuleName()); + + if (cpanel != null) + add(cpanel, BorderLayout.SOUTH); + + this.cpanel = cpanel; + + pack(); + repaint(); + } + + /** + * Report a fatal error, + * + * @param message + * The message, eg the exception message, to report to the user. + */ + @Override + public void fatalError(String message) { + FrontendError.reportFatal(message, this); + } + + /** + * @return the resource directory. + */ + @Override + public String getResourceDirectory() { + return "res/"; + } } diff --git a/src/uk/org/ury/frontend/FrontendModule.java b/src/uk/org/ury/frontend/FrontendModule.java index 4d1e3bf..8298ac5 100644 --- a/src/uk/org/ury/frontend/FrontendModule.java +++ b/src/uk/org/ury/frontend/FrontendModule.java @@ -4,38 +4,32 @@ package uk.org.ury.frontend; /** - * Interface for all system modules that are to be reachable from - * the frontend array. + * Interface for all system modules that are to be reachable from the frontend + * array. * * Frontend-exposed modules must: * * - be runnable standalone, as either an application or an applet; * - * - contain their user interface in a subclass of FrontendModulePanel - * which can be embedded either in a FrontendFrame, a web page - * or another module; - * + * - contain their user interface in a subclass of FrontendModulePanel which can + * be embedded either in a FrontendFrame, a web page or another module; + * * - use the frontend error reporting systems. * - * An abstract implementation of this interface, - * AbstractFrontendModule, is provided to simplify the creation of - * frontend modules. + * An abstract implementation of this interface, AbstractFrontendModule, is + * provided to simplify the creation of frontend modules. * * @author Matt Windsor - * + * */ - -public interface FrontendModule -{ - /** - * Begin execution of the frontend module. - * - * @param master The FrontendMaster driving the frontend. - * - * @return the frontend panel to insert into the - * FrontendMaster. - */ - - public FrontendModulePanel - runFrontend (FrontendMaster master); +public interface FrontendModule { + /** + * Begin execution of the frontend module. + * + * @param master + * The FrontendMaster driving the frontend. + * + * @return the frontend panel to insert into the FrontendMaster. + */ + public FrontendModulePanel runFrontend(FrontendMaster master); } diff --git a/src/uk/org/ury/frontend/client/Client.java b/src/uk/org/ury/frontend/client/Client.java new file mode 100644 index 0000000..9606d89 --- /dev/null +++ b/src/uk/org/ury/frontend/client/Client.java @@ -0,0 +1,60 @@ +package uk.org.ury.frontend.client; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +import java.util.Map; + +import uk.org.ury.common.protocol.ProtocolUtils; +import uk.org.ury.common.protocol.exceptions.DecodeFailureException; + +public class Client { + /** + * Get a raw response from the server. + * + * @param file + * The "file", including path and query string, to fetch from the + * server. + * + * @return The response from the server, as a key-value map. + * + * @throws DecodeFailureException + * if the decode failed. + */ + public Map get(String file) throws DecodeFailureException { + URL url = null; + URLConnection uc = null; + String result = ""; + + try { + url = new URL("http://localhost:8000" + file); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + uc = url.openConnection(); + + BufferedReader in = new BufferedReader(new InputStreamReader( + uc.getInputStream())); + + String inputLine; + + for (inputLine = in.readLine(); inputLine != null; inputLine = in + .readLine()) { + result += inputLine; + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return ProtocolUtils.decode(result); + } +} diff --git a/src/uk/org/ury/frontend/client/test/ClientTest.java b/src/uk/org/ury/frontend/client/test/ClientTest.java new file mode 100644 index 0000000..d75a0ac --- /dev/null +++ b/src/uk/org/ury/frontend/client/test/ClientTest.java @@ -0,0 +1,57 @@ +/** + * + */ +package uk.org.ury.frontend.client.test; + +import java.util.Map; + +import junit.framework.Assert; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import uk.org.ury.common.protocol.Directive; +import uk.org.ury.common.protocol.exceptions.DecodeFailureException; +import uk.org.ury.frontend.client.Client; + +/** + * JUnit test for the low-level client logic. + * + * @author Matt Windsor + */ +public class ClientTest { + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link uk.org.ury.frontend.client.Client#get(java.lang.String)}. + */ + @Test + public void testGet() { + Client client = new Client(); + + Map response = null; + + try { + response = client.get("/server/ServerRequestHandler?function=test"); + } catch (DecodeFailureException e) { + e.printStackTrace(); + } + + Assert.assertEquals("Test succeeded.", + response.get(Directive.INFO.toString())); + } +} diff --git a/src/uk/org/ury/frontend/cpanels/menu/DemoControlPanel.java b/src/uk/org/ury/frontend/cpanels/menu/DemoControlPanel.java new file mode 100644 index 0000000..7ef0907 --- /dev/null +++ b/src/uk/org/ury/frontend/cpanels/menu/DemoControlPanel.java @@ -0,0 +1,47 @@ +package uk.org.ury.frontend.cpanels.menu; + + +import uk.org.ury.frontend.FrontendControlPanel; +import uk.org.ury.frontend.exceptions.UICreationFailureException; + + +/** + * Control panel for the demo system. + * + * @author Matt Windsor + * + */ + +public class DemoControlPanel extends FrontendControlPanel +{ + + /** + * + */ + private static final long serialVersionUID = 7558888612002013312L; + + + /** + * Constructs a new DemoControlPanel. + * + * @throws UICreationFailureException if the UI creation fails. + */ + + public + DemoControlPanel () + throws UICreationFailureException + { + super ("demo_control_panel.xml"); + } + + + /** + * Go back to the previous module. + */ + + public void + back () + { + master.restoreModule (parent, prevCPanel); + } +} diff --git a/src/uk/org/ury/frontend/cpanels/menu/demo_control_panel.xml b/src/uk/org/ury/frontend/cpanels/menu/demo_control_panel.xml new file mode 100644 index 0000000..a9b5329 --- /dev/null +++ b/src/uk/org/ury/frontend/cpanels/menu/demo_control_panel.xml @@ -0,0 +1,7 @@ + + + +