diff --git a/src/main/java/edu/group5/app/App.java b/src/main/java/edu/group5/app/App.java index 782922c..d657fb8 100644 --- a/src/main/java/edu/group5/app/App.java +++ b/src/main/java/edu/group5/app/App.java @@ -3,10 +3,12 @@ import edu.group5.app.control.MainController; import edu.group5.app.control.wrapper.DbWrapper; import edu.group5.app.control.wrapper.OrgApiWrapper; +import edu.group5.app.model.donation.Donation; import edu.group5.app.model.donation.DonationRepository; import edu.group5.app.model.donation.DonationService; import edu.group5.app.model.organization.OrganizationRepository; import edu.group5.app.model.organization.OrganizationService; +import edu.group5.app.model.user.User; import edu.group5.app.model.user.UserRepository; import edu.group5.app.model.user.UserService; import javafx.application.Application; @@ -16,19 +18,30 @@ import java.util.List; +import java.util.logging.Logger; + /** * Main entry point for the Help-Me-Help charity donation application. * Handles database connection, data loading, and application setup. */ public class App extends Application { + DbWrapper dbWrapper; + UserRepository userRepository; + DonationRepository donationRepository; + private Logger logger; + private MainController controller; + private Scene scene; + @Override - public void start(Stage stage) { - DbWrapper dbWrapper = new DbWrapper(true); + public void init() { + this.logger = Logger.getLogger(App.class.getName()); + this.logger.info("Application starting"); + + this.dbWrapper = new DbWrapper(false); OrgApiWrapper orgApiWrapper = new OrgApiWrapper("https://app.innsamlingskontrollen.no/api/public/v1/all"); - if (!dbWrapper.connect()) { - System.err.println("Failed to connect to database"); - return; + while (!dbWrapper.connect()) { + this.logger.warning("Failed to connect to database"); } // Load data from database @@ -47,26 +60,40 @@ public void start(Stage stage) { } // Create repositories with fetched data - UserRepository userRepository = new UserRepository(userData); - DonationRepository donationRepository = new DonationRepository(donationData); + this.userRepository = new UserRepository(userData); + this.donationRepository = new DonationRepository(donationData); OrganizationRepository organizationRepository = new OrganizationRepository(organizationData); // Create services (backend wiring) - UserService userService = new UserService(userRepository); - DonationService donationService = new DonationService(donationRepository, organizationRepository); + UserService userService = new UserService(this.userRepository); + DonationService donationService = new DonationService(this.donationRepository, organizationRepository); OrganizationService organizationService = new OrganizationService(organizationRepository); - MainController controller = new MainController(userService, donationService, organizationService); + this.controller = new MainController(userService, donationService, organizationService); - Scene scene = controller.getMainView().getScene(); - controller.showLoginPage(); + this.scene = controller.getMainView().getScene(); + } + + @Override + public void start(Stage stage) { + this.controller.showLoginPage(); stage.getIcons().add(new Image(getClass().getResource("/header/images/hmh-logo.png").toExternalForm())); stage.setTitle("Help-Me-Help"); - stage.setScene(scene); + stage.setScene(this.scene); stage.show(); } + @Override + public void stop() throws Exception { + super.stop(); + this.logger.info("Application stopping"); + this.dbWrapper.connect(); + this.dbWrapper.exportUsers(this.userRepository.export()); + this.dbWrapper.exportDonations(this.donationRepository.export()); + this.dbWrapper.disconnect(); + } + public static void main(String[] args) { launch(args); } diff --git a/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java b/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java index babb929..7e3adc0 100644 --- a/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java +++ b/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java @@ -95,10 +95,13 @@ public List importUsers() { public int exportUsers(List data) { this.importUsers(); - + if (data == null) { throw new IllegalArgumentException("data can't be null"); } + if (data.isEmpty()) { + return 0; + } if (data.get(0).length != 6) { throw new IllegalArgumentException("data's arrays must have a length of 6"); } @@ -184,10 +187,13 @@ private List importDonations(int user_id, boolean all) { public int exportDonations(List data) { this.fetchAllDonations(); - + if (data == null) { throw new IllegalArgumentException("data can't be null"); } + if (data.isEmpty()) { + return 0; + } if (data.get(0).length != 6) { throw new IllegalArgumentException("data's arrays must have a length of 6"); } diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java index f3363b7..3cd1fa9 100644 --- a/src/main/java/edu/group5/app/model/DBRepository.java +++ b/src/main/java/edu/group5/app/model/DBRepository.java @@ -1,18 +1,21 @@ package edu.group5.app.model; + import java.util.HashMap; import java.util.Map; import java.util.List; + /** * Abstract base class for repositories that store their data * in a database-related structure. * *

- * Extends {@link Repository} and specifies that the content - * is stored as a {@link Map}. + * Extends {@link Repository} and specifies that the content + * is stored as a {@link Map}. *

*/ public abstract class DBRepository extends Repository { protected final Map contentLock; + /** * Constructs a DBRepository with the given content. * @@ -23,19 +26,18 @@ protected DBRepository(Map content) { this.contentLock = new HashMap<>(); } - protected void updateContentLock() { - synchronized (contentLock) { - contentLock.clear(); - contentLock.putAll(this.content); - } - } + protected abstract void updateContentLock(); public abstract boolean addContent(V value); /** - * Exports the repository content as a list of Object arrays, where each array represents a row of data. - * This method is intended for converting the repository content into a format suitable for database storage or export. - * @return a List of Object arrays representing the repository content for database export + * Exports the repository content as a list of Object arrays, where each array + * represents a row of data. + * This method is intended for converting the repository content into a format + * suitable for database storage or export. + * + * @return a List of Object arrays representing the repository content for + * database export */ public abstract List export(); } diff --git a/src/main/java/edu/group5/app/model/donation/DonationRepository.java b/src/main/java/edu/group5/app/model/donation/DonationRepository.java index 6181460..a55ea74 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationRepository.java +++ b/src/main/java/edu/group5/app/model/donation/DonationRepository.java @@ -15,7 +15,7 @@ * Repository class for Donation. * *

- * Extends {@link DBRepository} and manages Donation entities. + * Extends {@link DBRepository} and manages Donation entities. *

*/ public class DonationRepository extends DBRepository { @@ -23,10 +23,12 @@ public class DonationRepository extends DBRepository { /** * Constructs DonationRepository from a list of Object[] rows from the database. - * @param rows List of Object[] representing donations from the DB. - * Each row must have 6 elements: - * [donationId, userId, organizationId, amount, date, paymentMethod] - * @throws IllegalArgumentException if the input list is null or any row is invalid + * + * @param rows List of Object[] representing donations from the DB. + * Each row must have 6 elements: + * [donationId, userId, organizationId, amount, date, paymentMethod] + * @throws IllegalArgumentException if the input list is null or any row is + * invalid */ public DonationRepository(List rows) { super(new HashMap<>()); @@ -35,12 +37,12 @@ public DonationRepository(List rows) { } this.content = new HashMap<>(); for (Object[] row : rows) { - if (row == null || row.length != 6 ) { + if (row == null || row.length != 6) { throw new IllegalArgumentException("Each row must contain exactly 6 elements"); } int donationId = (int) row[0]; int customerId = (int) row[1]; - int organizationId = (int) row[2]; + int organizationId = (int) row[2]; BigDecimal amount = (BigDecimal) row[3]; Timestamp date = (Timestamp) row[4]; String paymentMethod = (String) row[5]; @@ -48,23 +50,39 @@ public DonationRepository(List rows) { Donation donation = new Donation(donationId, customerId, organizationId, amount, date, paymentMethod); this.content.put(donationId, donation); } - super.updateContentLock(); + this.updateContentLock(); + } + + @Override + protected void updateContentLock() { + synchronized (contentLock) { + this.contentLock.clear(); + this.contentLock.putAll(this.content); + } } @Override public List export() { - return content.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(entry -> { Donation donation = entry.getValue(); - return new Object[] { - donation.donationId(), donation.userId(), - donation.organizationId(), donation.amount(), - donation.date(), donation.paymentMethod()};}) - .toList(); + Map output = new HashMap<>(this.content); + for (int i : super.contentLock.keySet()) { + output.remove(i); + } + this.updateContentLock(); + return output.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> { + Donation donation = entry.getValue(); + return new Object[] { + donation.donationId(), donation.userId(), + donation.organizationId(), donation.amount(), + donation.date(), donation.paymentMethod() }; + }) + .toList(); } /** * Retrieves a donation by its ID. + * * @param donationId the ID of the donation to retrieve * @return the Donation object with the specified ID, or null if not found * @throws IllegalArgumentException if the donationId is not positive @@ -77,10 +95,12 @@ public Donation getDonationById(int donationId) { } /** - * Generates the next donation ID based on the current maximum ID in the repository. + * Generates the next donation ID based on the current maximum ID in the + * repository. + * * @return the next donation ID to be used for a new donation */ - public int getNextDonationId() { + public int getNextDonationId() { return content.keySet().stream().max(Integer::compareTo).orElse(0) + 1; } /* TODO change this when data database is introduced */ @@ -91,22 +111,22 @@ public Map getAllDonations() { /** * Adds a new donation to the repository *

- * The donation is stored using its {@code donationId} as the key. - * If a donation with the same ID already exists, the donation - * will not be added. + * The donation is stored using its {@code donationId} as the key. + * If a donation with the same ID already exists, the donation + * will not be added. *

* * @param donation the donation to add * @return {@code true} if the donation was successfully added, and - * {@code false} if a donation with the same ID already exists + * {@code false} if a donation with the same ID already exists */ @Override public boolean addContent(Donation donation) { if (donation == null) { throw new IllegalArgumentException("Donation cannot be null"); } - if (content.containsKey(donation.donationId())){ - return false; + if (content.containsKey(donation.donationId())) { + return false; } this.content.put(donation.donationId(), donation); return true; @@ -116,12 +136,12 @@ public boolean addContent(Donation donation) { * Returns all donations sorted by date (ascending). * *

- * The returned map preserves the sorted order. + * The returned map preserves the sorted order. *

* * @return a new {@link HashMap} containing the donations sorted by date */ - public HashMap sortByDate(){ + public HashMap sortByDate() { return content.entrySet().stream() .sorted(Map.Entry.comparingByValue( Comparator.comparing(Donation::date))) @@ -136,7 +156,7 @@ public HashMap sortByDate(){ * Returns all donations sorted by amount (ascending). * *

- * The returned map preserves the sorted order from lowest to highest amount. + * The returned map preserves the sorted order from lowest to highest amount. *

* * @return a new {@link HashMap} containing the donations sorted by amount. @@ -149,12 +169,12 @@ public HashMap sortByAmount() { Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, - LinkedHashMap::new - )); + LinkedHashMap::new)); } /** * Returns all donations associated with a specific organization. + * * @param orgNumber the organization ID to filter by * @return a map containing all donations that belong to the given organization * @throws IllegalArgumentException if the orgNumber is not positive @@ -165,17 +185,17 @@ public HashMap filterByOrganization(int orgNumber) { } return content.entrySet() .stream() - .filter(entry -> entry.getValue().organizationId() == orgNumber) + .filter(entry -> entry.getValue().organizationId() == orgNumber) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, - LinkedHashMap::new - )); + LinkedHashMap::new)); } /** * Returns all donations made by a specific user. + * * @param userId the user ID to filter by * @return a map containing all donations that belong to the given user * @throws IllegalArgumentException if the userId is not positive @@ -190,7 +210,6 @@ public HashMap filterByUser(int userId) { Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, - LinkedHashMap::new - )); + LinkedHashMap::new)); } } diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java index 692126f..193f433 100644 --- a/src/main/java/edu/group5/app/model/user/UserRepository.java +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -6,11 +6,13 @@ import edu.group5.app.model.DBRepository; -public class UserRepository extends DBRepository{ +public class UserRepository extends DBRepository { public final static String ROLE_CUSTOMER = "Customer"; + /** * Constructs UserRepository using Hashmap, * and extends the content from DBRepository. + * * @param content the underlying map used to store users, * where the key represents the user ID */ @@ -20,7 +22,7 @@ public UserRepository(List rows) { throw new IllegalArgumentException("The list of rows cannot be null"); } for (Object[] row : rows) { - if (row == null || row.length != 6 ) { + if (row == null || row.length != 6) { throw new IllegalArgumentException("Each row must contain exactly 6 elements"); } int userId = (int) row[0]; @@ -38,28 +40,46 @@ public UserRepository(List rows) { } this.content.put(userId, user); } - super.updateContentLock(); + this.updateContentLock(); } + @Override + protected void updateContentLock() { + synchronized (contentLock) { + this.contentLock.clear(); + this.contentLock.putAll(this.content); + } + } @Override public List export() { - return content.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(entry -> { User user = entry.getValue(); - return new Object[]{user.getUserId(), user.getRole(), - user.getFirstName(), user.getLastName(), - user.getEmail(), user.getPasswordHash()};}) + Map output = new HashMap<>(this.content); + for (int i : contentLock.keySet()) { + output.remove(i); + } + this.updateContentLock(); + return output.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> { + User user = entry.getValue(); + return new Object[] { user.getUserId(), user.getRole(), + user.getFirstName(), user.getLastName(), + user.getEmail(), user.getPasswordHash() }; + }) .toList(); + } public HashMap getUsers() { return new HashMap<>(content); } + /** * Retrieves a user by their unique identifier. + * * @param userId the unique identifier of the user to retrieve - * @return the user with the specified ID, or {@code null} if no such user exists + * @return the user with the specified ID, or {@code null} if no such user + * exists * @throws IllegalArgumentException if the userId is not positive */ public User getUserById(int userId) { @@ -72,12 +92,13 @@ public User getUserById(int userId) { /** * Generates the next user ID based on repository size. * Uses size+1 and then moves forward if that ID is already taken. + * * @return the next available user ID * @throws IllegalStateException if no available user ID can be found */ - public int getNextUserId() { + public int getNextUserId() { if (content.isEmpty()) { - return 1; + return 1; } int maxKey = content.keySet().stream().max(Integer::compareTo).orElseThrow( () -> new IllegalStateException("No keys found")); @@ -88,21 +109,21 @@ public int getNextUserId() { /** * Adds a new user to the repository *

- * The user is stored using its {@code userId} as the key. - * If a user with the same ID already exists, the user - * will not be added. + * The user is stored using its {@code userId} as the key. + * If a user with the same ID already exists, the user + * will not be added. *

* * @param user the user to add * @return {@code true} if the user was successfully added, and - * {@code false} if a user with the same ID already exists + * {@code false} if a user with the same ID already exists */ @Override public boolean addContent(User user) { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } - if (content.containsKey(user.getUserId())){ + if (content.containsKey(user.getUserId())) { return false; } this.content.put(user.getUserId(), user); @@ -111,8 +132,10 @@ public boolean addContent(User user) { /** * Finds a user by their email address. + * * @param email the email address of the user to find - * @return the user with the specified email address, or {@code null} if no such user exists + * @return the user with the specified email address, or {@code null} if no such + * user exists */ public User findUserByEmail(String email) { if (email == null || email.trim().isEmpty()) { @@ -122,5 +145,5 @@ public User findUserByEmail(String email) { .filter(user -> user.getEmail().equals(email)) .findFirst() .orElse(null); - } + } } diff --git a/src/test/java/edu/group5/app/control/wrapper/DbWrapperDonationsTest.java b/src/test/java/edu/group5/app/control/wrapper/DbWrapperDonationsTest.java index f31d57c..8c401c4 100644 --- a/src/test/java/edu/group5/app/control/wrapper/DbWrapperDonationsTest.java +++ b/src/test/java/edu/group5/app/control/wrapper/DbWrapperDonationsTest.java @@ -196,4 +196,12 @@ public void addingDonationListWithNullInRowThrowsExpectedException() { assertTrue(this.db.disconnect()); assertEquals("One or more rows in data contains null values", exception.getMessage()); } + + @Test + public void dataIsEmptyAfterExportingAndImportingEmptyList() { + assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); + assertEquals(0, this.db.exportDonations(new ArrayList())); + assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); + assertTrue(this.db.disconnect()); + } } diff --git a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java index bdf0110..ce81b2a 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java @@ -263,4 +263,13 @@ void filterByUserIdThrowsIfNegative() { () -> repo.filterByUser(0)); assertEquals("User ID must be positive", ex.getMessage()); } + + @Test + void exportExportsOnlyNewInformation() { + repo.addContent(donation1); + repo.addContent(donation2); + assertEquals(2, repo.export().size()); + repo.addContent(donation3); + assertEquals(1, repo.export().size()); + } } \ No newline at end of file diff --git a/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java index 0700828..a4afbc8 100644 --- a/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java @@ -12,6 +12,7 @@ public class UserRepositoryTest { private UserRepository repo; private List rows; +private User additionalUser; @BeforeEach void setUp() { @@ -19,6 +20,7 @@ void setUp() { rows.add(new Object[]{1, "Customer", "John", "Cena", "john@example.com", "hashedpass"}); rows.add(new Object[]{2, "Customer", "Jane", "Doe", "jane@example.com", "hashedpass"}); repo = new UserRepository(rows); + this.additionalUser = new Customer(3, "John", "Doe", "john@example.com", "hashedpass"); } @Test @@ -145,4 +147,11 @@ void exportContainsAllUsers() { assertEquals(2, exported.get(1)[0]); assertEquals("Customer", exported.get(0)[1]); } + + @Test + void exportExportsOnlyNewInformation() { + assertEquals(0, repo.export().size()); + repo.addContent(this.additionalUser); + assertEquals(1, repo.export().size()); + } } \ No newline at end of file