aboutsummaryrefslogtreecommitdiff
path: root/src/uk/org/ury/backend
diff options
context:
space:
mode:
authorMatt Windsor <mattwindsor@btinternet.com>2011-03-21 21:54:31 +0000
committerMatt Windsor <mattwindsor@btinternet.com>2011-03-21 21:54:31 +0000
commitdf7d7981b56a4560c95ea7e9b194080e93398ecf (patch)
treeb3ae4f02d23ae1f7f4951c776ee8d91b0047dd6f /src/uk/org/ury/backend
parent2d073129857a42ab4195cd433c8be152e357033f (diff)
GREAT PACKAGE RESHUFFLE: Everything is now organised into frontend, backend and common (to frontend and backend) packages. Things may have been broken. Doc refresh.
Diffstat (limited to 'src/uk/org/ury/backend')
-rw-r--r--src/uk/org/ury/backend/config/Auth.java36
-rw-r--r--src/uk/org/ury/backend/config/ConfigReader.java115
-rw-r--r--src/uk/org/ury/backend/config/Database.java45
-rw-r--r--src/uk/org/ury/backend/database/DatabaseDriver.java166
-rw-r--r--src/uk/org/ury/backend/database/DatabaseItem.java80
-rw-r--r--src/uk/org/ury/backend/database/UserClass.java38
-rw-r--r--src/uk/org/ury/backend/database/exceptions/ConnectionFailureException.java44
-rw-r--r--src/uk/org/ury/backend/database/exceptions/MissingCredentialsException.java52
-rw-r--r--src/uk/org/ury/backend/database/exceptions/MissingPropertyException.java47
-rw-r--r--src/uk/org/ury/backend/database/exceptions/QueryFailureException.java45
-rw-r--r--src/uk/org/ury/backend/database/exceptions/package.html11
-rw-r--r--src/uk/org/ury/backend/database/package.html15
-rw-r--r--src/uk/org/ury/backend/handlers/LibraryRequestHandler.java189
-rw-r--r--src/uk/org/ury/backend/server/AbstractRequestHandler.java254
-rw-r--r--src/uk/org/ury/backend/server/ApiRequestHandler.java42
-rw-r--r--src/uk/org/ury/backend/server/HttpHandler.java205
-rw-r--r--src/uk/org/ury/backend/server/HttpListenerThread.java128
-rw-r--r--src/uk/org/ury/backend/server/HttpWorkerThread.java104
-rw-r--r--src/uk/org/ury/backend/server/Server.java99
-rw-r--r--src/uk/org/ury/backend/server/ServerRequestHandler.java95
-rw-r--r--src/uk/org/ury/backend/server/exceptions/BadRequestException.java47
-rw-r--r--src/uk/org/ury/backend/server/exceptions/HandleFailureException.java46
-rw-r--r--src/uk/org/ury/backend/server/exceptions/HandlerNotFoundException.java51
-rw-r--r--src/uk/org/ury/backend/server/exceptions/HandlerSetupFailureException.java55
-rw-r--r--src/uk/org/ury/backend/server/exceptions/HandlingException.java48
-rw-r--r--src/uk/org/ury/backend/server/exceptions/NotAHandlerException.java34
-rw-r--r--src/uk/org/ury/backend/server/exceptions/UnknownFunctionException.java37
-rw-r--r--src/uk/org/ury/backend/server/package.html23
28 files changed, 2151 insertions, 0 deletions
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<E, T> {
+ private Map<E, T> 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<E, T> 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<String, String> asResponse() {
+ // TODO: Fan out implementation details into separate class
+ Map<String, String> response = new HashMap<String, String>();
+
+ 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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+
+<HTML>
+ <HEAD>
+ <TITLE>uk.org.ury.database.exceptions</TITLE>
+ </HEAD>
+ <BODY>
+ <P>Exceptions thrown by the database services classes.</P>
+ </BODY>
+</HTML> \ 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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+
+<HTML>
+ <HEAD>
+ <TITLE>uk.org.ury.database</TITLE>
+ </HEAD>
+ <BODY>
+ <P>Database services for the URY Presenter Suite.</P>
+ <P>The classes provided within this package are expected to be
+ used by the back-end through utility classes, and <EM>not</EM>
+ by the frontend, which should use the server API to indirectly
+ query the database.</P>
+ </BODY>
+</HTML> \ 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<String, String> parameters,
+ Map<String, Object> 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<Map<String, String>> itemArray = new ArrayList<Map<String, String>>();
+
+ 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<String, Object> content = new HashMap<String, Object>();
+
+ 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<String, Object> content = new HashMap<String, Object>();
+
+ 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<String, String> parseQueryString(String query)
+ throws UnsupportedEncodingException {
+ Map<String, String> params = new HashMap<String, String>();
+
+ // 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<String, Object>
+ handleGetRequest (Map<String, String> 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
+ * <http://www.apache.org/>.
+ */
+
+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<String, String> parameters;
+
+ try {
+ parameters = parseQueryString(queryString);
+ } catch (UnsupportedEncodingException e) {
+ throw new HandlerSetupFailureException(className, e);
+ }
+
+ Map<String, Object> 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
+ * <http://www.apache.org/>.
+ */
+
+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
+ * <http://www.apache.org/>.
+ */
+
+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 = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
+ + "\"http://www.w3.org/TR/html4/strict.dtd\">";
+ public static final String INDEX_HTML = "\n<html>" + "\n <head>"
+ + "\n <title>" + SERVER_VERSION + "</title>" + "\n </head>"
+ + "\n <body>" + "\n <h1>Welcome to the " + SERVER_VERSION
+ + " server</h1>"
+ + "\n <p>This server exposes a class-based API for accessing"
+ + "\n the internals of the " + SERVER_VERSION + " system.</p>"
+ + "\n <p>See the documentation for details.</p>" + "\n </body>"
+ + "\n</html>";
+
+ /**
+ * 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<String, Object>
+ handleGetRequest (Map<String, String> parameters, Server server)
+ throws HandleFailureException
+ {
+ Map<String, Object> response = new HashMap<String, Object> ();
+
+ 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<String, Object> 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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+
+<HTML>
+ <HEAD>
+ <TITLE>uk.org.ury.server</TITLE>
+ </HEAD>
+ <BODY>
+ <P>The URY Server kernel.</P>
+ <P>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.</P>
+ <P>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.</P>
+ <P>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
+ </BODY>
+</HTML> \ No newline at end of file