Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package me.camerongray.teamlocker.server; import com.google.common.io.Resources; import com.mchange.v2.c3p0.ComboPooledDataSource; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.beanutils.DynaBean; import org.json.JSONArray; import org.json.JSONObject; import org.mindrot.jbcrypt.BCrypt; import static spark.Spark.*; /** * * @author camerong */ public class Server { public static void main(String[] args) throws PropertyVetoException, SQLException { ConnectionManager.initialise("localhost", "teamlocker", "teamlocker", "teamlocker"); before((request, response) -> { // Log request StringBuilder sb = new StringBuilder(); sb.append(request.requestMethod()); sb.append(" " + request.url()); sb.append(" " + request.body()); System.out.println(sb); if (request.headers("Authorization") == null) { response.header("WWW-Authenticate", "Basic"); halt(401); } RequestCredentials credentials = new RequestCredentials(request); if (!Auth.checkCredentials(credentials.username, credentials.password)) { ResponseBuilder.errorHalt(response, 401, "Incorrect username/password"); } }); get("/check_auth/", (request, response) -> { return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); get("/users/:userId/", (request, response) -> { DynaBean user = null; try (Database database = new Database(ConnectionManager.getConnection(request))) { if (request.params(":userId").equals("self")) { try { user = database.getUser((new RequestCredentials(request)).getUsername()); } catch (ObjectNotFoundException e) { ResponseBuilder.errorHalt(response, 404, "User not found"); } } else { Auth.enforceAdmin(request, response); try { user = database.getUser(Integer.parseInt(request.params(":userId"))); } catch (NumberFormatException e) { ResponseBuilder.errorHalt(response, 400, "User ID must be a number"); } catch (ObjectNotFoundException e) { ResponseBuilder.errorHalt(response, 404, "User not found"); } } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("user", ResponseBuilder.objectOf("id", (int) user.get("id"), "full_name", (String) user.get("full_name"), "username", (String) user.get("username"), "email", (String) user.get("email"), "auth_hash", (String) user.get("auth_hash"), "encrypted_private_key", (String) user.get("encrypted_private_key"), "public_key", (String) user.get("public_key"), "admin", (boolean) user.get("admin"), "pbkdf2_salt", (String) user.get("pbkdf2_salt"), "aes_iv", (String) user.get("aes_iv")))); }); get("/users/:userId/encrypted_aes_keys/", (request, response) -> { int userId; if (request.params(":userId").equals("self")) { userId = Auth.getCurrentUserId(request); } else { Auth.enforceAdmin(request, response); userId = Integer.parseInt(request.params(":userId")); } List<DynaBean> accountData; try (Database database = new Database(ConnectionManager.getConnection(request))) { accountData = database.getUserAccountData(userId); } ArrayList<JSONObject> aesKeyObjects = new ArrayList<>(); for (DynaBean accountDataItem : accountData) { aesKeyObjects.add(ResponseBuilder.objectOf("account_id", (int) accountDataItem.get("account_id"), "encrypted_aes_key", (String) accountDataItem.get("encrypted_aes_key"))); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("encrypted_aes_keys", ResponseBuilder.fromArrayList(aesKeyObjects))); }); get("/users/:userId/permissions/", (request, response) -> { Auth.enforceAdmin(request, response); List<DynaBean> permissions = new ArrayList<>(); try (Database database = new Database(ConnectionManager.getConnection(request))) { try { permissions = database.getUserPermissions(Integer.parseInt(request.params(":userId"))); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "User ID must be a number"); } } ArrayList<JSONObject> responseObjects = new ArrayList<>(); for (DynaBean permission : permissions) { responseObjects.add(ResponseBuilder.objectOf("folder_id", (int) permission.get("folder_id"), "read", (boolean) permission.get("read"), "write", (boolean) permission.get("write"))); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("permissions", ResponseBuilder.fromArrayList(responseObjects))); }); delete("/users/:userId/permissions/", (request, response) -> { Auth.enforceAdmin(request, response); try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.deleteUserPermissions(Integer.parseInt(request.params(":userId"))); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "User ID must be a number"); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); post("/users/:userId/", (request, response) -> { Auth.enforceAdmin(request, response); int userId = -1; try { userId = Integer.parseInt(request.params(":userId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "User ID must be a number"); } JSONObject requestJson = null; try { requestJson = RequestJson.getValidated(request, "postUsers"); } catch (JSONValidationException ex) { // TODO: Friendly error messages for JSONValidationExceptions rather than raw output from validation library ResponseBuilder.errorHalt(response, 400, ex.getMessage()); } try (Database database = new Database(ConnectionManager.getConnection(request))) { boolean usernameExists = true; try { DynaBean user = database.getUser(requestJson.getString("username")); if ((Integer) user.get("id") == userId) { usernameExists = false; } } catch (ObjectNotFoundException ex) { usernameExists = false; } if (usernameExists) { ResponseBuilder.errorHalt(response, 409, "A user with that username already exists"); } database.updateUser(userId, requestJson.getString("username"), requestJson.getString("full_name"), requestJson.getString("email"), requestJson.getBoolean("admin")); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); get("/users/", (request, response) -> { Auth.enforceAdmin(request, response); ArrayList<JSONObject> userObjects = new ArrayList<>(); try (Database database = new Database(ConnectionManager.getConnection(request))) { List<DynaBean> users = database.getAllUsers(); for (DynaBean user : users) { userObjects.add(ResponseBuilder.objectOf("id", (int) user.get("id"), "full_name", (String) user.get("full_name"), "username", (String) user.get("username"), "email", (String) user.get("email"), "auth_hash", (String) user.get("auth_hash"), "encrypted_private_key", (String) user.get("encrypted_private_key"), "public_key", (String) user.get("public_key"), "admin", (boolean) user.get("admin"), "pbkdf2_salt", (String) user.get("pbkdf2_salt"), "aes_iv", (String) user.get("aes_iv"))); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("users", ResponseBuilder.fromArrayList(userObjects))); }); put("/users/", (request, response) -> { JSONObject requestJson = null; try { requestJson = RequestJson.getValidated(request, "putUsers"); } catch (JSONValidationException ex) { // TODO: Friendly error messages for JSONValidationExceptions rather than raw output from validation library ResponseBuilder.errorHalt(response, 400, ex.getMessage()); } int userId = -1; try (Database database = new Database(ConnectionManager.getConnection(request))) { boolean userExists = true; try { database.getUser(requestJson.getString("username")); } catch (ObjectNotFoundException ex) { userExists = false; } if (userExists) { ResponseBuilder.errorHalt(response, 409, "A user with that username already exists"); } userId = database.addUser(requestJson.getString("full_name"), requestJson.getString("username"), requestJson.getString("email"), BCrypt.hashpw(requestJson.getString("auth_key"), BCrypt.gensalt()), requestJson.getString("encrypted_private_key"), requestJson.getString("public_key"), requestJson.getBoolean("admin"), requestJson.getString("pbkdf2_salt"), requestJson.getString("aes_iv")); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("user_id", userId)); }); get("/folders/", (request, response) -> { ArrayList<JSONObject> folderObjects = new ArrayList<>(); try (Database database = new Database(ConnectionManager.getConnection(request))) { List<DynaBean> folders = database.getFolders((int) Auth.getCurrentUser(request).get("id")); for (DynaBean folder : folders) { folderObjects.add(ResponseBuilder.objectOf("id", (int) folder.get("id"), "name", (String) folder.get("name"), "read", (boolean) folder.get("read"), "write", (boolean) folder.get("write"))); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("folders", ResponseBuilder.fromArrayList(folderObjects))); }); put("/folders/", (request, response) -> { JSONObject requestJson = null; try { requestJson = RequestJson.getValidated(request, "putFolder"); } catch (JSONValidationException ex) { // TODO: Friendly error messages for JSONValidationExceptions rather than raw output from validation library ResponseBuilder.errorHalt(response, 400, ex.getMessage()); } Auth.enforceAdmin(request, response); int folderId = -1; try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.getFolder(requestJson.getString("name")); ResponseBuilder.errorHalt(response, 409, "A folder with that name already exists"); } catch (ObjectNotFoundException ex) { // We don't care if it doesn't exist, we actually want this exception to be thrown! } folderId = database.addFolder(requestJson.getString("name")); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("folder_id", folderId)); }); post("/folders/:folderId/", (request, response) -> { int folderId = -1; try { folderId = Integer.parseInt(request.params(":folderId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Folder ID must be a number"); } Auth.enforceFolderPermission(request, response, folderId, Auth.PERMISSION_WRITE); JSONObject requestJson = null; try { requestJson = RequestJson.getValidated(request, "postFolders"); } catch (JSONValidationException ex) { // TODO: Friendly error messages for JSONValidationExceptions rather than raw output from validation library ResponseBuilder.errorHalt(response, 400, ex.getMessage()); } try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.updateFolder(folderId, requestJson.getString("name")); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Folder not found"); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); // TODO - Replace with generic update user method post("/users/self/update_password/", (request, response) -> { JSONObject requestJson = null; try { requestJson = RequestJson.getValidated(request, "postUsersUpdatePassword"); } catch (JSONValidationException ex) { // TODO: Friendly error messages for JSONValidationExceptions rather than raw output from validation library ResponseBuilder.errorHalt(response, 400, ex.getMessage()); } try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.updateUserPassword(Auth.getCurrentUserId(request), requestJson.getString("encrypted_private_key"), requestJson.getString("aes_iv"), requestJson.getString("pbkdf2_salt"), BCrypt.hashpw(requestJson.getString("auth_key"), BCrypt.gensalt())); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "User not found"); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); delete("/folders/:folderId/", (request, response) -> { int folderId = -1; try { folderId = Integer.parseInt(request.params(":folderId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Folder ID must be a number"); } Auth.enforceFolderPermission(request, response, folderId, Auth.PERMISSION_WRITE); try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.deleteFolder(folderId); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Folder not found"); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); get("/folders/:folderId/accounts/", (request, response) -> { int folderId = -1; try { folderId = Integer.parseInt(request.params(":folderId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Folder ID must be a number"); } Auth.enforceFolderPermission(request, response, folderId, Auth.PERMISSION_READ); ArrayList<JSONObject> accountObjects = new ArrayList<>(); try (Database database = new Database(ConnectionManager.getConnection(request))) { List<DynaBean> accounts = new ArrayList<>(); accounts = database.getFolderAccounts(folderId, Auth.getCurrentUserId(request)); for (DynaBean account : accounts) { accountObjects.add(ResponseBuilder.objectOf("id", (int) account.get("account_id"), "account_metadata", (String) account.get("account_metadata"), "encrypted_aes_key", (String) account.get("encrypted_aes_key"))); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("accounts", ResponseBuilder.fromArrayList(accountObjects))); }); get("/folders/:folderId/permissions/", (request, response) -> { Auth.enforceAdmin(request, response); List<DynaBean> permissions = new ArrayList<>(); try (Database database = new Database(ConnectionManager.getConnection(request))) { try { permissions = database.getFolderPermissions(Integer.parseInt(request.params(":folderId"))); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Folder ID must be a number"); } } ArrayList<JSONObject> responseObjects = new ArrayList<>(); for (DynaBean permission : permissions) { responseObjects.add(ResponseBuilder.objectOf("user_id", (int) permission.get("user_id"), "read", (boolean) permission.get("read"), "write", (boolean) permission.get("write"))); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("permissions", ResponseBuilder.fromArrayList(responseObjects))); }); post("/folders/:folderId/permissions/", (request, response) -> { Auth.enforceAdmin(request, response); int folderId = -1; try { folderId = Integer.parseInt(request.params(":folderId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Folder ID must be a number"); } JSONObject requestJson = RequestJson.getValidated(request, "postFoldersPermissions"); try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.getFolder(folderId); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Folder not found!"); } TransactionInterface transaction; try { transaction = new Transaction(database.getWrappedConnection()); } catch (ExistingOpenTransactionException ex) { transaction = new NullTransaction(); } try { database.deleteFolderPermissions(folderId); JSONArray permissions = requestJson.getJSONArray("permissions"); for (int i = 0; i < permissions.length(); i++) { JSONObject permission = permissions.getJSONObject(i); int userId = permission.getInt("user_id"); boolean read = permission.getBoolean("read"); boolean write = permission.getBoolean("write"); try { DynaBean user = database.getUser(userId); if ((boolean) user.get("admin")) { transaction.rollback(); ResponseBuilder.errorHalt(response, 400, "Trying to set permissions " + "for an administrator, administrators already have full permission."); } } catch (ObjectNotFoundException ex) { transaction.rollback(); ResponseBuilder.errorHalt(response, 404, "User not found!"); } if (write && !read) { transaction.rollback(); ResponseBuilder.errorHalt(response, 400, "Users must be able " + "to read a folder if they are to write to it"); } if (!(write || read)) { database.deleteAccountDataForFolder(folderId, userId); } else { database.addPermission(folderId, userId, read, write); } } } catch (SQLException ex) { transaction.rollback(); ResponseBuilder.errorHalt(response, 500, "Error updating permissions - " + ex); } transaction.commit(); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); get("/folders/:folderId/public_keys/", (request, response) -> { int folderId = -1; try { folderId = Integer.parseInt(request.params(":folderId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Folder ID must be a number"); } Auth.enforceFolderPermission(request, response, folderId, Auth.PERMISSION_WRITE); List<DynaBean> users; try (Database database = new Database(ConnectionManager.getConnection(request))) { users = database.getFolderUsers(folderId); } ArrayList<JSONObject> publicKeyObjects = new ArrayList<>(); for (DynaBean user : users) { publicKeyObjects.add(ResponseBuilder.objectOf("user_id", (int) user.get("id"), "public_key", (String) user.get("public_key"))); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("public_keys", ResponseBuilder.fromArrayList(publicKeyObjects))); }); get("/accounts/:accountId/", (request, response) -> { int accountId = -1; try { accountId = Integer.parseInt(request.params(":accountId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Account ID must be a number"); } Auth.enforceAccountPermission(request, response, accountId, Auth.PERMISSION_READ); DynaBean account = null; try (Database database = new Database(ConnectionManager.getConnection(request))) { try { account = database.getAccountData(accountId, Auth.getCurrentUserId(request)); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Account not found"); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("account", ResponseBuilder.objectOf("account_metadata", (String) account.get("account_metadata"), "encrypted_aes_key", (String) account.get("encrypted_aes_key")))); }); delete("/accounts/:accountId/", (request, response) -> { int accountId = -1; try { accountId = Integer.parseInt(request.params(":accountId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Account ID must be a number"); } Auth.enforceAccountPermission(request, response, accountId, Auth.PERMISSION_WRITE); try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.deleteAccount(accountId); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Account not found"); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); post("/accounts/:accountId/", (request, response) -> { JSONObject requestJson = null; try { requestJson = RequestJson.getValidated(request, "postAccountsSingle"); } catch (JSONValidationException ex) { // TODO: Friendly error messages for JSONValidationExceptions rather than raw output from validation library ResponseBuilder.errorHalt(response, 400, "JSON Validation Error - " + ex.getMessage()); } int accountId = -1; try { accountId = Integer.parseInt(request.params(":accountId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Account ID must be a number"); } Auth.enforceAccountPermission(request, response, accountId, Auth.PERMISSION_WRITE); try (Database database = new Database(ConnectionManager.getConnection(request))) { try { database.getAccount(accountId); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Account not found"); } try { database.getFolder(requestJson.getInt("folder_id")); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Folder not found"); } TransactionInterface transaction; try { transaction = new Transaction(database.getWrappedConnection()); } catch (ExistingOpenTransactionException ex) { transaction = new NullTransaction(); } try { database.updateAccount(accountId, requestJson.getInt("folder_id")); JSONArray accountDataItems = requestJson.getJSONArray("encrypted_account_data"); for (int i = 0; i < accountDataItems.length(); i++) { JSONObject accountDataItem = accountDataItems.getJSONObject(i); int userId = accountDataItem.getInt("user_id"); database.deleteAccountData(accountId, userId); database.addAccountDataItem(accountId, userId, accountDataItem.getString("account_metadata"), accountDataItem.getString("password"), accountDataItem.getString("encrypted_aes_key")); } } catch (SQLException ex) { transaction.rollback(); ResponseBuilder.errorHalt(response, 500, "Error saving account - " + ex); } catch (ObjectNotFoundException ex) { transaction.rollback(); ResponseBuilder.errorHalt(response, 404, "Error saving account - Object Not Found"); } transaction.commit(); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); post("/accounts/", (request, response) -> { JSONObject requestJson = RequestJson.getValidated(request, "postAccountsBatch"); JSONArray accounts = requestJson.getJSONArray("accounts"); try (Database database = new Database(ConnectionManager.getConnection(request))) { TransactionInterface transaction; try { transaction = new Transaction(database.getWrappedConnection()); } catch (ExistingOpenTransactionException ex) { transaction = new NullTransaction(); } try { for (int i = 0; i < accounts.length(); i++) { JSONObject account = accounts.getJSONObject(i); int accountId = account.getInt("account_id"); try { if (!Auth.getAccountPermission(request, response, accountId, Auth.PERMISSION_WRITE)) { transaction.rollback(); ResponseBuilder.errorHalt(response, 403, "You do not have write permission for account " + accountId); } } catch (ObjectNotFoundException ex) { transaction.rollback(); ex.printStackTrace(); ResponseBuilder.errorHalt(response, 404, "Account not found"); } JSONArray accountDataItems = account.getJSONArray("encrypted_account_data"); for (int j = 0; j < accountDataItems.length(); j++) { JSONObject accountDataItem = accountDataItems.getJSONObject(j); int userId = accountDataItem.getInt("user_id"); database.deleteAccountData(accountId, userId); database.addAccountDataItem(accountId, userId, accountDataItem.getString("account_metadata"), accountDataItem.getString("password"), accountDataItem.getString("encrypted_aes_key")); } } } catch (SQLException ex) { transaction.rollback(); ResponseBuilder.errorHalt(response, 500, "Error saving accounts - " + ex); } transaction.commit(); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); put("/accounts/", (request, response) -> { JSONObject requestJson = null; try { requestJson = RequestJson.getValidated(request, "putAccounts"); } catch (JSONValidationException ex) { // TODO: Friendly error messages for JSONValidationExceptions rather than raw output from validation library ResponseBuilder.errorHalt(response, 400, ex.getMessage()); } Auth.enforceFolderPermission(request, response, requestJson.getInt("folder_id"), Auth.PERMISSION_WRITE); int accountId = -1; WrappedConnection connection = ConnectionManager.getConnection(request); try (Database database = new Database(connection)) { try { database.getFolder(requestJson.getInt("folder_id")); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Folder not found"); } TransactionInterface transaction; try { transaction = new Transaction(connection); } catch (ExistingOpenTransactionException ex) { transaction = new NullTransaction(); Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex); } try { accountId = database.addAccount(requestJson.getInt("folder_id")); JSONArray accountDataItems = requestJson.getJSONArray("encrypted_account_data"); for (int i = 0; i < accountDataItems.length(); i++) { JSONObject accountDataItem = accountDataItems.getJSONObject(i); database.addAccountDataItem(accountId, accountDataItem.getInt("user_id"), accountDataItem.getString("account_metadata"), accountDataItem.getString("password"), accountDataItem.getString("encrypted_aes_key")); } } catch (SQLException ex) { transaction.rollback(); ResponseBuilder.errorHalt(response, 500, "Error adding account - " + ex); } transaction.commit(); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("account_id", accountId)); }); get("/accounts/:accountId/password/", (request, response) -> { int accountId = -1; try { accountId = Integer.parseInt(request.params(":accountId")); } catch (NumberFormatException ex) { ResponseBuilder.errorHalt(response, 400, "Account ID must be a number"); } Auth.enforceAccountPermission(request, response, accountId, Auth.PERMISSION_READ); DynaBean account = null; try (Database database = new Database(ConnectionManager.getConnection(request))) { try { account = database.getAccountData(accountId, Auth.getCurrentUserId(request)); } catch (ObjectNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Account not found"); } } return ResponseBuilder.build(response, ResponseBuilder.objectOf("password", ResponseBuilder.objectOf("encrypted_password", (String) account.get("password"), "encrypted_aes_key", (String) account.get("encrypted_aes_key")))); }); put("/transaction/", (request, response) -> { Transaction transaction = TransactionStore.getTransaction(); return ResponseBuilder.build(response, ResponseBuilder.objectOf("transaction_id", transaction.getId())); }); post("/transaction/:transactionId/commit/", (request, response) -> { String transactionId = request.params(":transactionId"); try { Transaction transaction = TransactionStore.getTransaction(transactionId); transaction.commit(); transaction.getWrappedConnection().getConnection().close(); TransactionStore.forgetTransaction(transactionId); } catch (TransactionNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Transaction not found!"); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); post("/transaction/:transactionId/rollback/", (request, response) -> { String transactionId = request.params(":transactionId"); try { Transaction transaction = TransactionStore.getTransaction(transactionId); transaction.rollback(); transaction.getWrappedConnection().getConnection().close(); TransactionStore.forgetTransaction(transactionId); } catch (TransactionNotFoundException ex) { ResponseBuilder.errorHalt(response, 404, "Transaction not found!"); } return ResponseBuilder.build(response, ResponseBuilder.objectOf("success", true)); }); exception(Exception.class, (e, request, response) -> { if (e.getClass().equals(TransactionNotFoundException.class)) { response.status(404); response.body(ResponseBuilder.objectOf("error", true, "message", "Transaction not found", "type", "transaction_not_found").toString()); } else { System.out.println("An unhandled exception occurred!"); System.out.println(e.toString()); e.printStackTrace(); response.status(500); response.type("application/json"); response.body(ResponseBuilder .objectOf("error", true, "message", "An unhandled server error occurred! - " + e.toString()) .toString()); } }); //TODO - Disable this in production! // spark.debug.DebugScreen.enableDebugScreen(); } }