-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' of git.ntnu.no:cathrkri/systemutviklingTeam6 i…
…nto feature/58-final-delivery-updated-database # Conflicts: # helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java # helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java # helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java # helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseManagerTest.java
- Loading branch information
Showing
33 changed files
with
4,259 additions
and
412 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
384 changes: 384 additions & 0 deletions
384
helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,384 @@ | ||
| package ntnu.systemutvikling.team6.database; | ||
|
|
||
| import java.sql.*; | ||
| import java.util.*; | ||
|
|
||
| import ntnu.systemutvikling.team6.models.Charity; | ||
| import ntnu.systemutvikling.team6.models.CharityRegistry; | ||
| import ntnu.systemutvikling.team6.models.Donation; | ||
| import ntnu.systemutvikling.team6.models.DonationRegistry; | ||
| import ntnu.systemutvikling.team6.scraper.APICharityData; | ||
| import ntnu.systemutvikling.team6.scraper.LogoDownloader; | ||
| import ntnu.systemutvikling.team6.scraper.URLCharityScraper; | ||
|
|
||
| /** | ||
| * Manages the Database with MySQL tables and JDBC. | ||
| * | ||
| * <p>This class is responsible for creating the tables needed for the application, if not done | ||
| * already and maintaining the {@code charities} table based on data retrieved from the IK API. It | ||
| * is also responsible for retrieving the data from the database and sending it to the application | ||
| * through the CharityRegistry and DonationRegistry. It is used by the FrontpageController to | ||
| * retrieve the data needed to display the charities | ||
| */ | ||
| public class DatabaseManager { | ||
| private final DatabaseConnection connection; | ||
|
|
||
| /** | ||
| * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection | ||
| * credentials. | ||
| */ | ||
| public DatabaseManager() { | ||
| this.connection = new DatabaseConnection(); | ||
| } | ||
|
|
||
| /** | ||
| * Connection test for the Database. Does a simple SELECT SQL query and returns either true og and | ||
| * Exception if failed | ||
| * | ||
| * @return true if Sucsedd or SQLExepction if failed | ||
| */ | ||
| public boolean testConnection() { | ||
| try (Connection conn = connection.getMySqlConnection(); | ||
| Statement stmt = conn.createStatement()) { | ||
|
|
||
| ResultSet rs = stmt.executeQuery("SELECT 1"); | ||
|
|
||
| if (rs.next()) { | ||
| System.out.println("Database connection verified."); | ||
| return true; | ||
| } | ||
|
|
||
| } catch (SQLException e) { | ||
| System.out.println("Database connection failed."); | ||
| e.printStackTrace(); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Creates the {@code Charities} and {@code Donations} tables if they do not already exist. | ||
| * | ||
| * <p>The table structure for Charities is based on fields from {@link APICharityData}. | ||
| * | ||
| * @throws RuntimeException if a {@link SQLException} occurs while creating the table | ||
| */ | ||
| public void createTables() { | ||
| String sql_query1 = | ||
| """ | ||
| -- ----------------------------------------------------- | ||
| -- Table `HelpMeHelp`.`Charities` | ||
| -- ----------------------------------------------------- | ||
| CREATE TABLE IF NOT EXISTS Charities ( | ||
| UUID_charities CHAR(36) PRIMARY KEY, | ||
| org_number VARCHAR(255) NOT NULL, | ||
| charity_name VARCHAR(255) NOT NULL, | ||
| charity_link VARCHAR(255) NOT NULL, | ||
| pre_approved TINYINT NOT NULL, | ||
| status VARCHAR(255) NOT NULL, | ||
| description TEXT, | ||
| logoURL TEXT, | ||
| categories TEXT, | ||
| key_values TEXT, | ||
| logoBlob MEDIUMBLOB, | ||
| UNIQUE KEY unique_org_number (org_number) | ||
| ) ENGINE=InnoDB; | ||
| """; | ||
| String sql_query2 = | ||
| """ | ||
| -- ----------------------------------------------------- | ||
| -- Table `HelpMeHelp`.`Donations` | ||
| -- ----------------------------------------------------- | ||
| CREATE TABLE IF NOT EXISTS Donations ( | ||
| `UUID_Donations` CHAR(36) NOT NULL, | ||
| `amount` DECIMAL NOT NULL, | ||
| `date` DATE NOT NULL, | ||
| `Charities_UUID_charities` CHAR(36) NOT NULL, | ||
| PRIMARY KEY (`UUID_Donations`), | ||
| INDEX `fk_Donations_Charities_idx` (`Charities_UUID_charities` ASC) VISIBLE, | ||
| CONSTRAINT `fk_Donations_Charities` | ||
| FOREIGN KEY (`Charities_UUID_charities`) | ||
| REFERENCES Charities (`UUID_charities`) | ||
| ON DELETE CASCADE | ||
| ON UPDATE CASCADE) | ||
| ENGINE = InnoDB; | ||
| """; | ||
|
|
||
| try (Connection conn = connection.getMySqlConnection(); | ||
| Statement s = conn.createStatement()) { | ||
|
|
||
| s.execute(sql_query1); | ||
| s.execute(sql_query2); | ||
| } catch (SQLException e) { | ||
| e.printStackTrace(); | ||
| throw new RuntimeException("Error creating table."); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * This method is used to verify the integrity of the data in the {@code charities} table and to | ||
| * update it based on the data retrieved from the IK API and the charity's URL. | ||
| * The param charities are retrieved from | ||
| * the IK API through the APICharityData class. Called in initialize method in | ||
| * HmHApplication.java, which is the main class of the application, to ensure that the data is up | ||
| * to date when the application starts. Uses a temp table to ensure that the data in the database | ||
| * is consistent with the data from the API. | ||
| * <p>Uses a URLScraper object to get data not contained in the API, and static methods from | ||
| * LogoDownloader to get the charity's logo as a blob.</p> | ||
| * | ||
| * @param charities a list of {@code Charity} objects to add to the database | ||
| */ | ||
| public void addAPIDataToTable(List<Charity> charities) { | ||
| Connection conn = null; | ||
| int charityCounter = 0; | ||
|
|
||
| // Scrapes description, logo, categories, and key values from IK | ||
| for (Charity charity : charities) { | ||
| charityCounter++; | ||
|
|
||
| System.out.println("Scraping charity " + charityCounter + " of " + charities.size()); | ||
| URLCharityScraper urlScraper = new URLCharityScraper(charity.getURL()); | ||
| urlScraper.scrapeCharityPage(); | ||
|
|
||
| charity.setDescription(urlScraper.getDescription()); | ||
| charity.setCategory(urlScraper.getCategories()); | ||
| charity.setLogoURL(urlScraper.getLogoURL()); | ||
| charity.setKeyValues(urlScraper.getKeyValues()); | ||
| byte[] logoBlob = LogoDownloader.downloadImageAsBlob(charity.getLogoURL()); | ||
| charity.setLogoBlob(logoBlob); | ||
| } | ||
| try { | ||
| conn = connection.getMySqlConnection(); | ||
| conn.setAutoCommit(false); | ||
| String sql_query = | ||
| """ | ||
| INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, pre_approved, status, description, logoURL, categories, key_values, logoBlob) | ||
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | ||
| ON DUPLICATE KEY UPDATE | ||
| charity_name = VALUES(charity_name), | ||
| charity_link = VALUES(charity_link), | ||
| pre_approved = VALUES(pre_approved), | ||
| status = VALUES(status), | ||
| description = VALUES(description), | ||
| logoURL = VALUES(logoURL), | ||
| categories = VALUES(categories), | ||
| key_values = VALUES(key_values), | ||
| logoBlob = VALUES(logoBlob) | ||
| """; | ||
|
|
||
| try (PreparedStatement ps = conn.prepareStatement(sql_query)) { | ||
| for (Charity charity : charities) { | ||
| if (charity.getUUID() == null) { | ||
| ps.setString(1, UUID.randomUUID().toString()); | ||
| } else { | ||
| ps.setString(1, charity.getUUID().toString()); | ||
| } | ||
|
|
||
| ps.setString(2, charity.getOrg_number().replaceAll("\\s", "")); | ||
| ps.setString(3, charity.getName()); | ||
| ps.setString(4, charity.getURL()); | ||
| ps.setBoolean(5, charity.getPreApproved()); | ||
| ps.setString(6, charity.getStatus()); | ||
| ps.setString(7, charity.getDescription()); | ||
| ps.setString(8, charity.getLogoURL()); | ||
| ps.setString(9, charity.getCategory()); | ||
| ps.setString(10, charity.getKeyValues()); | ||
| ps.setBytes(11, charity.getLogoBlob()); | ||
|
|
||
| ps.addBatch(); | ||
| } | ||
| ps.executeBatch(); | ||
| } | ||
|
|
||
| // -- Intergerty Check: | ||
| String createTemp = | ||
| """ | ||
| CREATE TEMPORARY TABLE temp_api_charities ( | ||
| org_number VARCHAR(20) PRIMARY KEY | ||
| ) | ||
| """; | ||
|
|
||
| try (PreparedStatement ps = conn.prepareStatement(createTemp)) { | ||
| ps.execute(); | ||
| } | ||
|
|
||
| String insertTemp = "INSERT IGNORE INTO temp_api_charities (org_number) VALUES (?)"; | ||
|
|
||
| try (PreparedStatement ps = conn.prepareStatement(insertTemp)) { | ||
|
|
||
| for (Charity charity : charities) { | ||
| ps.setString(1, charity.getOrg_number().replaceAll("\\s", "")); | ||
| ps.addBatch(); | ||
| } | ||
|
|
||
| ps.executeBatch(); | ||
| } | ||
|
|
||
| String deleteSql = | ||
| """ | ||
| DELETE FROM Charities c | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM temp_api_charities t | ||
| WHERE t.org_number = c.org_number | ||
| ) | ||
| """; | ||
|
|
||
| try (PreparedStatement ps = conn.prepareStatement(deleteSql)) { | ||
| ps.executeUpdate(); | ||
| } | ||
|
|
||
| conn.commit(); | ||
|
|
||
| } catch (SQLException e) { | ||
| if (conn != null) { | ||
| try { | ||
| conn.rollback(); | ||
| } catch (SQLException ex) { | ||
| ex.printStackTrace(); | ||
| } | ||
| } | ||
| e.printStackTrace(); | ||
|
|
||
| throw new RuntimeException("ERROR: Something went wrong during updating charities table."); | ||
| } finally { | ||
| if (conn != null) { | ||
| try { | ||
| conn.setAutoCommit(true); | ||
| conn.close(); | ||
| } catch (SQLException e) { | ||
| e.printStackTrace(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Fetches the data stored in the database and converts it to a list of Charity objects | ||
| * in the form of a registry (CharityRegistry). | ||
| * | ||
| * @return a CharityRegistry of all the charities registered in the database | ||
| */ | ||
| public CharityRegistry getCharitiesFromDB() { | ||
| CharityRegistry registry = null; | ||
| Connection conn = null; | ||
| try { | ||
| conn = connection.getMySqlConnection(); | ||
| String sql_query = | ||
| "SELECT UUID_charities, org_number, charity_name, charity_link, pre_approved, status, description, logoURL, " + | ||
| "categories, key_values, logoBlob FROM Charities"; | ||
| Statement stmt = conn.createStatement(); | ||
| ResultSet rs = stmt.executeQuery(sql_query); | ||
|
|
||
| registry = new CharityRegistry(); | ||
| while (rs.next()) { | ||
| Charity charity = | ||
| new Charity( | ||
| rs.getString("UUID_charities"), | ||
| rs.getString("org_number"), | ||
| rs.getString("charity_link"), | ||
| rs.getString("charity_name"), | ||
| rs.getBoolean("pre_approved"), | ||
| rs.getString("status")); | ||
| charity.setDescription(rs.getString("description")); | ||
| charity.setLogoURL(rs.getString("logoURL")); | ||
| charity.setCategory(rs.getString("categories")); | ||
| charity.setKeyValues(rs.getString("key_values")); | ||
| charity.setLogoBlob(rs.getBytes("logoBlob")); | ||
|
|
||
| registry.addCharity(charity); | ||
| } | ||
| } catch (SQLException e) { | ||
| e.printStackTrace(); | ||
| throw new RuntimeException("ERROR: Something went wrong during updating charities table."); | ||
| } | ||
| return registry; | ||
| } | ||
|
|
||
| public List<String> getCategoriesFromDB() { | ||
| Map<String, String> categoryMap = new HashMap<>(); | ||
|
|
||
| String sql_query = "SELECT categories FROM Charities"; | ||
|
|
||
| try (Connection conn = connection.getMySqlConnection(); | ||
| Statement stmt = conn.createStatement(); | ||
| ResultSet rs = stmt.executeQuery(sql_query)) { | ||
|
|
||
| while (rs.next()) { | ||
| String categoriesStr = rs.getString("categories"); | ||
|
|
||
| if (categoriesStr != null && !categoriesStr.isEmpty()) { | ||
| String[] splitCategories = categoriesStr.split(","); | ||
|
|
||
| for (String category : splitCategories) { | ||
| String trimmed = category.trim(); | ||
|
|
||
| if (!trimmed.isEmpty()) { | ||
| categoryMap.putIfAbsent(trimmed.toLowerCase(), trimmed); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } catch (SQLException e) { | ||
| e.printStackTrace(); | ||
| throw new RuntimeException("ERROR: Something went wrong while fetching categories from the database."); | ||
| } | ||
|
|
||
| var categories = new ArrayList<>(categoryMap.values()); | ||
| Collections.sort(categories); | ||
|
|
||
| return categories; | ||
| } | ||
|
|
||
| public DonationRegistry getDonationFromDB() { | ||
| DonationRegistry registry = null; | ||
| Connection conn = null; | ||
| try { | ||
| conn = connection.getMySqlConnection(); | ||
| String sql_query = | ||
| """ | ||
| SELECT | ||
| d.UUID_Donations, | ||
| d.amount, | ||
| d.date, | ||
| c.UUID_charities, | ||
| c.org_number, | ||
| c.charity_name, | ||
| c.charity_link, | ||
| c.pre_approved, | ||
| c.status | ||
| FROM Donations d | ||
| JOIN Charities c | ||
| ON d.Charities_UUID_charities = c.UUID_charities | ||
| """; | ||
| Statement stmt = conn.createStatement(); | ||
| ResultSet rs = stmt.executeQuery(sql_query); | ||
|
|
||
| registry = new DonationRegistry(); | ||
| while (rs.next()) { | ||
| Charity charity = | ||
| new Charity( | ||
| rs.getString("UUID_charities"), | ||
| rs.getString("org_number"), | ||
| rs.getString("charity_name"), | ||
| rs.getString("charity_link"), | ||
| rs.getBoolean("pre_approved"), | ||
| rs.getString("status")); | ||
|
|
||
| Donation donation = | ||
| new Donation( | ||
| rs.getString("UUID_Donations"), | ||
| rs.getDouble("amount"), | ||
| rs.getDate("date").toLocalDate(), | ||
| charity); | ||
| registry.addDonation(donation); | ||
| } | ||
| } catch (SQLException e) { | ||
| e.printStackTrace(); | ||
| throw new RuntimeException("ERROR: Something went wrong during updating charities table."); | ||
| } | ||
| return registry; | ||
| } | ||
| } |
Oops, something went wrong.