Skip to content

Commit

Permalink
perf: unify repository constructors, unify Backend classes, standardi…
Browse files Browse the repository at this point in the history
…ze exports, update services, and add comprehensive tests
  • Loading branch information
Fredrik Marjoni committed Mar 7, 2026
1 parent 6c34cfc commit 4ac5cdd
Show file tree
Hide file tree
Showing 15 changed files with 949 additions and 178 deletions.
35 changes: 30 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,37 @@
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
<classifier>win</classifier>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>7.0.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.3.5</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/tools.jackson.core/jackson-databind -->
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.1.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -111,6 +123,19 @@
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>META-INF/NOTICE</exclude>
<exclude>META-INF/LICENSE</exclude>
<exclude>META-INF.versions.9.module-info</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>edu.group5.app.App</mainClass>
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/edu/group5/app/model/DBRepository.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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.
Expand All @@ -10,12 +12,34 @@
* </p>
*/
public abstract class DBRepository<K, V> extends Repository<K, V> {
protected final Map<K, V> contentLock;
/**
* Constructs a DBRepository with the given content.
*
* @param content the HashMap used to store repository entities
*/
protected DBRepository(Map<K, V> content) {
super(content);
this.contentLock = new HashMap<>();
}

protected void updateContentLock() {
synchronized (contentLock) {
contentLock.clear();
contentLock.putAll(this.content);
}
}

public void addContent(K key, V value) {
synchronized (contentLock) {
content.put(key, 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
*/
public abstract List<Object[]> export();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.List;
import java.math.BigDecimal;
import java.sql.Timestamp;

/**
* Repository class for Donation.
Expand All @@ -19,17 +22,70 @@ public class DonationRepository extends DBRepository<Integer, Donation> {
private final HashMap<Integer, Donation> content;

/**
* Constructs DonationRepository using Hashmap,
* and extends the content from DBRepository.
* @param content the underlying map used to store donations,
* where the key represents the donation ID
* 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
*/
public DonationRepository(HashMap<Integer, Donation> content){
if (content == null) {
throw new IllegalArgumentException("Content cannot be null");
public DonationRepository(List<Object[]> rows) {
super(new HashMap<>());
if (rows == null) {
throw new IllegalArgumentException("The list of rows cannot be null");
}
super(content);
this.content = content;
this.content = new HashMap<>();
for (Object[] row : rows) {
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];
BigDecimal amount = (BigDecimal) row[3];
Timestamp date = (Timestamp) row[4];
String paymentMethod = (String) row[5];

Donation donation = new Donation(donationId, customerId, organizationId, amount, date, paymentMethod);
this.content.put(donationId, donation);
}
super.updateContentLock();
}

@Override
public List<Object[]> 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();
}

/**
* 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
*/
public Donation getDonationById(int donationId) {
if (donationId <= 0) {
throw new IllegalArgumentException("Donation ID must be positive");
}
return content.get(donationId);
}

/**
* 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() {
return content.keySet().stream().max(Integer::compareTo).orElse(0) + 1;
} /* TODO change this when data database is introduced */

public Map<Integer, Donation> getAllDonations() {
return new HashMap<>(content);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@ public DonationService(DonationRepository donationRepository,
* @return true if the donation is successfully processed, false otherwise
*/
public boolean donate(Customer customer, int orgNumber, BigDecimal amount, String paymentMethod) {
if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0 || paymentMethod.isBlank()) {
if (customer == null || amount == null
|| amount.compareTo(BigDecimal.ZERO) <= 0 || paymentMethod.isBlank()) {
return false;
}
Organization org = organizationRepository.findByOrgNumber(orgNumber);
if (org == null) {
return false;
}
Donation donation = new Donation(1, customer.getUserId(), org.orgNumber(), /* TODO fix incrementation of donation */
amount, Timestamp.from(Instant.now()), paymentMethod);
Donation donation =
new Donation(donationRepository.getNextDonationId(),
customer.getUserId(), org.orgNumber(), amount, Timestamp.from(Instant.now()), paymentMethod);
donationRepository.addDonation(donation);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,22 @@
import java.util.Map;

/**
* Repository class for managing Organization entities. It provides methods to retrieve trusted organizations,
* find organizations by their organization number or name, and initializes the repository with input data.
* The repository uses a HashMap to store Organization objects for efficient retrieval based on their organization number.
* Handles the business logic associated with organizations
*/
public class OrganizationRepository extends Repository<Integer, Organization> {
private final HashMap<Integer, Organization> grandMap;

/**
* Initializes the repository with the given input data, c
* onverting it into Organization objects and storing them in a map for efficient retrieval.
* The input is expected to be an array of objects, where each object contains
* the necessary information to create an Organization.
* @param input the input data used to populate the repository, must not be null
* @throws IllegalArgumentException if the input is null
*/
public OrganizationRepository(Object[] input) {
super(new HashMap<>());
grandMap = new HashMap<>();
Expand All @@ -27,23 +38,38 @@ public OrganizationRepository(Object[] input) {

String orgNumberStr = ((String) contentMap.get("org_number")).replaceAll("\\s", "");
int orgNumber = Integer.parseInt(orgNumberStr);

String name = (String) contentMap.get("name");

boolean trusted = "approved".equalsIgnoreCase((String) contentMap.get("status"));

String websiteURL = (String) contentMap.get("url");

boolean isPreApproved = Boolean.TRUE.equals(contentMap.get("is_pre_approved"));

String description = "Information about " + name;

Organization org = new Organization(orgNumber, name, trusted, websiteURL, isPreApproved, description);

grandMap.put(org.orgNumber(), org);
}
}

/**
* Exports the organization data in a format suitable for output, such as JSON. Each organization is represented as a map containing its attributes.
* @return an array of maps, where each map represents an organization with its attributes (org_number, name, status, url, is_pre_approved)
* @throws IllegalStateException if the repository is empty
*/
public Object[] export() {
if (grandMap.isEmpty()) {
throw new IllegalStateException("The repository is empty");
}
return grandMap.values().stream()
.map(org -> { Map<String, Object> orgMap = new HashMap<>();
orgMap.put("org_number", org.orgNumber());
orgMap.put("name", org.name());
orgMap.put("status", org.trusted() ? "approved" : "unapproved");
orgMap.put("url", org.websiteURL());
orgMap.put("is_pre_approved", org.isPreApproved());
return orgMap;
})
.toArray();
}

/**
* Gets all trusted organizations in the repository
* @return all organizations with trusted = true
Expand All @@ -59,11 +85,32 @@ public Map<Integer, Organization> getTrustedOrganizations() {
return trustedOrganizations;
}

/**
* Finds an organization by its organization number
* @param orgNumber the organization number of the Organization
* @return the Organization with the given organization number, or null if not found
* @throws IllegalArgumentException if the organization number is not a positive integer
*/
public Organization findByOrgNumber(int orgNumber) {
if (orgNumber == 0 || orgNumber <= 0) {
if (orgNumber <= 0) {
throw new IllegalArgumentException("The Organization number must be a positive integer");
}
return grandMap.get(orgNumber);
}

/**
* Finds an organization by its name, ignoring case
* @param name the name of the Organization
* @return the Organization with the given name, or null if not found
* @throws IllegalArgumentException if the name is null or empty
*/
public Organization findByOrgName(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("The name cannot be null");
}
return grandMap.values().stream()
.filter(org -> org.name().equalsIgnoreCase(name))
.findFirst()
.orElse(null);
}
}
28 changes: 6 additions & 22 deletions src/main/java/edu/group5/app/model/user/Customer.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/
public class Customer extends User {
private List<Integer> preferences;

/**
* Constructs a new Customer object for new registrations.
* The role is automatically set to "Customer" and the preferences list is initialized empty.
Expand All @@ -25,34 +24,19 @@ public class Customer extends User {
*/
public Customer(int userId, String firstName, String lastName,
String email, String passwordHash) {
super(userId, "Customer", firstName, lastName, email, passwordHash);
this.preferences = new ArrayList<>();
}

/**
* Constructs a Customer object for existing users loaded from the database
* or for cases where the role is already defined.
* The role should normally be "Customer", but this constructor allows flexibility
* (e.g., for testing or future user types). Preferences list is initialized empty.
*
* @param userId the unique identifier for the user, must be positive
* @param role the role of the user (should be "Customer" for customer instances)
* @param firstName the first name of the customer
* @param lastName the last name of the customer
* @param email the email address of the customer
* @param passwordHash the hashed password of the customer
* @throws IllegalArgumentException if any parameter is invalid (null, empty, or userId ≤ 0)
*/
public Customer(int userId, String role, String firstName, String lastName,
String email, String passwordHash) {
super(userId, role, firstName, lastName, email, passwordHash);
super(userId, firstName, lastName, email, passwordHash);
this.preferences = new ArrayList<>();
}

public List<Integer> getPreferences() {
return preferences;
}

@Override
public String getRole() {
return UserRepository.ROLE_CUSTOMER;
}

public void addPreference(int orgNumber) {
if (orgNumber <= 0) {
throw new IllegalArgumentException("Organization number must be a positive integer");
Expand Down
Loading

0 comments on commit 4ac5cdd

Please sign in to comment.