From d5f3549ba0f220f8ff32866a8bdd34b96c5cb2ef Mon Sep 17 00:00:00 2001 From: Eden Xu <130119691+FlyingPufferFish@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:57:50 +1000 Subject: [PATCH 1/5] feat(database): Add ability to store Room, Student, Unit to database --- backend/.gitignore | 2 +- .../src/main/java/org/acme/domain/Room.java | 28 +++++++++++------ .../java/org/acme/domain/RoomResource.java | 31 +++++++++++++++++++ .../main/java/org/acme/domain/Student.java | 12 +++++-- .../java/org/acme/domain/StudentResource.java | 29 +++++++++++++++++ .../src/main/java/org/acme/domain/Unit.java | 31 +++++++++++++------ .../java/org/acme/domain/UnitResource.java | 29 +++++++++++++++++ 7 files changed, 141 insertions(+), 21 deletions(-) create mode 100644 backend/src/main/java/org/acme/domain/RoomResource.java create mode 100644 backend/src/main/java/org/acme/domain/StudentResource.java create mode 100644 backend/src/main/java/org/acme/domain/UnitResource.java diff --git a/backend/.gitignore b/backend/.gitignore index 91a800a..b1e5177 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -22,7 +22,7 @@ bin/ nb-configuration.xml # Visual Studio Code -.vscode +.vscode/ .factorypath # OSX diff --git a/backend/src/main/java/org/acme/domain/Room.java b/backend/src/main/java/org/acme/domain/Room.java index 57c7bbd..dd712d8 100644 --- a/backend/src/main/java/org/acme/domain/Room.java +++ b/backend/src/main/java/org/acme/domain/Room.java @@ -1,18 +1,28 @@ package org.acme.domain; import ai.timefold.solver.core.api.domain.lookup.PlanningId; +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.OneToOne; /** * Represents a room. * * @author Jet Edge */ -public class Room { +@Entity +public class Room extends PanacheEntity { @PlanningId - private String id; + private String roomCode; - private int capacity; - private boolean isLab; + public String buildingId; + + public int capacity; + + public boolean isLab; + + @OneToOne + public Unit unit; public Room() { } @@ -25,17 +35,17 @@ public Room() { * @param isLab Whether the room is a laboratory. */ public Room(String id, int capacity, boolean isLab) { - this.id = id; + this.roomCode = id; this.capacity = capacity; this.isLab = isLab; } - public String getId() { - return id; + public String getRoomCode() { + return roomCode; } - public void setId(String id) { - this.id = id; + public void setRoomCode(String id) { + this.roomCode = id; } public int getCapacity() { diff --git a/backend/src/main/java/org/acme/domain/RoomResource.java b/backend/src/main/java/org/acme/domain/RoomResource.java new file mode 100644 index 0000000..c86a1a9 --- /dev/null +++ b/backend/src/main/java/org/acme/domain/RoomResource.java @@ -0,0 +1,31 @@ +package org.acme.domain; + +import java.util.List; + +import jakarta.transaction.Transactional; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +@Path("/rooms") +public class RoomResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + public List list() { + return Room.listAll(); + } + + @POST + @Transactional + @Consumes(MediaType.APPLICATION_JSON) + public Response createCampus(Room room) { + room.persist(); + return Response.status(Response.Status.CREATED).entity(room).build(); + } + +} diff --git a/backend/src/main/java/org/acme/domain/Student.java b/backend/src/main/java/org/acme/domain/Student.java index 42093dd..38f62ab 100644 --- a/backend/src/main/java/org/acme/domain/Student.java +++ b/backend/src/main/java/org/acme/domain/Student.java @@ -2,16 +2,24 @@ import java.util.Objects; +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; + /** * Represents a student. * * @author Jet Edge */ -public class Student { +@Entity +public class Student extends PanacheEntity{ // String studentID; - String name; + public String name; + + @ManyToOne + public Unit unit; public Student() { } diff --git a/backend/src/main/java/org/acme/domain/StudentResource.java b/backend/src/main/java/org/acme/domain/StudentResource.java new file mode 100644 index 0000000..996d015 --- /dev/null +++ b/backend/src/main/java/org/acme/domain/StudentResource.java @@ -0,0 +1,29 @@ +package org.acme.domain; + +import java.util.List; + +import jakarta.transaction.Transactional; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +@Path("/students") +public class StudentResource { + @GET + @Produces(MediaType.APPLICATION_JSON) + public List list() { + return Student.listAll(); + } + + @POST + @Transactional + @Consumes(MediaType.APPLICATION_JSON) + public Response createCampus(Student student) { + student.persist(); + return Response.status(Response.Status.CREATED).entity(student).build(); + } +} diff --git a/backend/src/main/java/org/acme/domain/Unit.java b/backend/src/main/java/org/acme/domain/Unit.java index 570c9b7..8c66e3b 100644 --- a/backend/src/main/java/org/acme/domain/Unit.java +++ b/backend/src/main/java/org/acme/domain/Unit.java @@ -3,6 +3,10 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import java.time.DayOfWeek; import java.time.Duration; @@ -15,22 +19,31 @@ * * @author Jet Edge */ +@Entity @PlanningEntity -public class Unit { +public class Unit extends PanacheEntity { + + @OneToMany(mappedBy = "unit", orphanRemoval = false) + public List students; - private List students; @PlanningId - private int unitID; - private String name; - private Duration duration; + public int unitID; + + public String name; + + public Duration duration; + @PlanningVariable - private DayOfWeek dayOfWeek; + public DayOfWeek dayOfWeek; + @PlanningVariable - private LocalTime startTime; + public LocalTime startTime; + + @OneToOne(mappedBy = "unit", orphanRemoval = false) @PlanningVariable - private Room room; + public Room room; - private boolean wantsLab; + public boolean wantsLab; public Unit() { } diff --git a/backend/src/main/java/org/acme/domain/UnitResource.java b/backend/src/main/java/org/acme/domain/UnitResource.java new file mode 100644 index 0000000..8455d46 --- /dev/null +++ b/backend/src/main/java/org/acme/domain/UnitResource.java @@ -0,0 +1,29 @@ +package org.acme.domain; + +import java.util.List; + +import jakarta.transaction.Transactional; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +@Path("/units") +public class UnitResource { + @GET + @Produces(MediaType.APPLICATION_JSON) + public List list() { + return Unit.listAll(); + } + + @POST + @Transactional + @Consumes(MediaType.APPLICATION_JSON) + public Response createCampus(Unit unit) { + unit.persist(); + return Response.status(Response.Status.CREATED).entity(unit).build(); + } +} From 5bcdf70754c2c9b92aef78c105148ec032217c76 Mon Sep 17 00:00:00 2001 From: Eden Xu <130119691+FlyingPufferFish@users.noreply.github.com> Date: Sun, 22 Sep 2024 17:47:17 +1000 Subject: [PATCH 2/5] feat(database): setup all database schemas --- .vscode/settings.json | 5 ++ .../main/java/org/acme/TimetableResource.java | 23 ++++-- .../domain/Campus.java | 2 +- .../domain/CampusResource.java | 2 +- .../src/main/java/org/acme/domain/Room.java | 23 ++++-- .../main/java/org/acme/domain/Student.java | 22 +++++- .../main/java/org/acme/domain/Timetable.java | 70 +++++++++++++++++-- .../src/main/java/org/acme/domain/Unit.java | 41 +++++++++-- .../src/main/resources/application.properties | 2 +- 9 files changed, 166 insertions(+), 24 deletions(-) create mode 100644 .vscode/settings.json rename backend/src/main/java/org/acme/{schooltimetabling => }/domain/Campus.java (89%) rename backend/src/main/java/org/acme/{schooltimetabling => }/domain/CampusResource.java (94%) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1bfdd24 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "conventionalCommits.scopes": [ + "database" + ] +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/TimetableResource.java b/backend/src/main/java/org/acme/TimetableResource.java index 72c1943..7eb4200 100644 --- a/backend/src/main/java/org/acme/TimetableResource.java +++ b/backend/src/main/java/org/acme/TimetableResource.java @@ -2,6 +2,7 @@ import ai.timefold.solver.core.api.solver.SolverManager; import jakarta.inject.Inject; +import jakarta.transaction.Transactional; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -38,8 +39,16 @@ public Timetable handleRequest(Timetable problem) throws ExecutionException, Int return solution; } + @Path("/view") @GET @Produces(MediaType.APPLICATION_JSON) + public List view() { + return Timetable.listAll(); + } + + @GET + @Transactional + @Produces(MediaType.APPLICATION_JSON) public Timetable hello() throws ExecutionException, InterruptedException { Student a = new Student("a"); @@ -56,12 +65,14 @@ public Timetable hello() throws ExecutionException, InterruptedException { Room r2 = new Room("Room2", 4, false); Room r3 = new Room("Room3", 4, false); + Unit u1 = new Unit(1, "1", Duration.ofHours(2), List.of(a, b), true); + Unit u2 = new Unit(2, "2", Duration.ofHours(2), List.of(a, c, d, e), true); + Unit u3 = new Unit(3, "3", Duration.ofHours(2), List.of(f, g, h, i), false); + Unit u4 = new Unit(4, "4", Duration.ofHours(2), List.of(a, b), false); + var problem = new Timetable( List.of( - new Unit(1, "1", Duration.ofHours(2), List.of(a, b), true), - new Unit(2, "2", Duration.ofHours(2), List.of(a, c, d, e), true), - new Unit(3, "3", Duration.ofHours(2), List.of(f, g, h, i), false), - new Unit(4, "4", Duration.ofHours(2), List.of(a, b), false) + u1, u2, u3, u4 // new Unit(5, "5", Duration.ofHours(2), List.of(c, d, e)), // new Unit(6, "6", Duration.ofHours(2), List.of(f, g, h, i)) ), @@ -83,8 +94,12 @@ public Timetable hello() throws ExecutionException, InterruptedException { List.of(r1, r2, r3) ); + problem.persist(); + Timetable solution = solverManager.solve("job 1", problem).getFinalBestSolution(); + // solution.persist(); + return solution; } diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/Campus.java b/backend/src/main/java/org/acme/domain/Campus.java similarity index 89% rename from backend/src/main/java/org/acme/schooltimetabling/domain/Campus.java rename to backend/src/main/java/org/acme/domain/Campus.java index d4c49cc..365844c 100644 --- a/backend/src/main/java/org/acme/schooltimetabling/domain/Campus.java +++ b/backend/src/main/java/org/acme/domain/Campus.java @@ -1,4 +1,4 @@ -package org.acme.schooltimetabling.domain; +package org.acme.domain; // import java.util.List; import jakarta.persistence.*; diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/CampusResource.java b/backend/src/main/java/org/acme/domain/CampusResource.java similarity index 94% rename from backend/src/main/java/org/acme/schooltimetabling/domain/CampusResource.java rename to backend/src/main/java/org/acme/domain/CampusResource.java index 7b380c9..f93e55e 100644 --- a/backend/src/main/java/org/acme/schooltimetabling/domain/CampusResource.java +++ b/backend/src/main/java/org/acme/domain/CampusResource.java @@ -1,4 +1,4 @@ -package org.acme.schooltimetabling.domain; +package org.acme.domain; import java.util.List; diff --git a/backend/src/main/java/org/acme/domain/Room.java b/backend/src/main/java/org/acme/domain/Room.java index dd712d8..b8ef648 100644 --- a/backend/src/main/java/org/acme/domain/Room.java +++ b/backend/src/main/java/org/acme/domain/Room.java @@ -1,9 +1,18 @@ package org.acme.domain; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonManagedReference; + import ai.timefold.solver.core.api.domain.lookup.PlanningId; import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; -import jakarta.persistence.OneToOne; +import jakarta.persistence.FetchType; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; /** * Represents a room. @@ -13,7 +22,7 @@ @Entity public class Room extends PanacheEntity { @PlanningId - private String roomCode; + public String roomCode; public String buildingId; @@ -21,8 +30,14 @@ public class Room extends PanacheEntity { public boolean isLab; - @OneToOne - public Unit unit; + @JsonIgnoreProperties("room") + @OneToMany(mappedBy = "room", orphanRemoval = false) + public List unit; + + @JsonIgnoreProperties("rooms") + @ManyToMany(mappedBy = "rooms", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @JsonManagedReference + public List timetables = new ArrayList(); public Room() { } diff --git a/backend/src/main/java/org/acme/domain/Student.java b/backend/src/main/java/org/acme/domain/Student.java index 38f62ab..146f729 100644 --- a/backend/src/main/java/org/acme/domain/Student.java +++ b/backend/src/main/java/org/acme/domain/Student.java @@ -1,10 +1,19 @@ package org.acme.domain; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonManagedReference; + import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; /** * Represents a student. @@ -18,8 +27,15 @@ public class Student extends PanacheEntity{ public String name; - @ManyToOne - public Unit unit; + @JsonIgnoreProperties("students") + @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @JoinTable( + name = "student_unit", + joinColumns = @JoinColumn(name = "student_id"), + inverseJoinColumns = @JoinColumn(name = "unit_id") + ) + @JsonManagedReference + public List units = new ArrayList(); public Student() { } diff --git a/backend/src/main/java/org/acme/domain/Timetable.java b/backend/src/main/java/org/acme/domain/Timetable.java index b6f9826..71e44a2 100644 --- a/backend/src/main/java/org/acme/domain/Timetable.java +++ b/backend/src/main/java/org/acme/domain/Timetable.java @@ -6,32 +6,75 @@ import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Transient; import java.time.DayOfWeek; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonManagedReference; + /** * Represents a timetable, the solution from the program. * * @author Jet Edge */ +@Entity @PlanningSolution -public class Timetable { - +public class Timetable extends PanacheEntity { + @ElementCollection @ValueRangeProvider - private List daysOfWeek; + public List daysOfWeek; + + @ElementCollection @ValueRangeProvider - private List startTimes; + public List startTimes; + /* + * Rooms can belong to multiple timetables because timetables are generated + * on a per-campus basis, and although each room can only belong to one + * campus, the user may choose to generate multiple timetables for each + * campus, hence the many-to-many relationship + */ + @JsonIgnoreProperties("timetables") + @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @JoinTable( + name = "room_timetable", + joinColumns = @JoinColumn(name = "timetable_id"), + inverseJoinColumns = @JoinColumn(name = "room_id") + ) + @JsonManagedReference @ProblemFactCollectionProperty @ValueRangeProvider - private List rooms; + public List rooms; + /* + * Units can belong to multiple timetables because again, timetables are + * generated on a per-campus basis, but each unit can be taught across + * multiple campuses, so may appear in multiple timetables + */ + @JsonIgnoreProperties("timetables") + @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @JoinTable( + name = "unit_timetable", + joinColumns = @JoinColumn(name = "timetable_id"), + inverseJoinColumns = @JoinColumn(name = "unit_id") + ) + @JsonManagedReference @PlanningEntityCollectionProperty - private List units; + public List units; + @Transient @PlanningScore private HardSoftScore score; @@ -48,6 +91,7 @@ public Timetable() { public Timetable(List units, List startTimes) { this.units = units; this.startTimes = startTimes; + this.setUnitTimetable(); } /** @@ -61,6 +105,8 @@ public Timetable(List units, List startTimes, List rooms) this.units = units; this.startTimes = startTimes; this.rooms = rooms; + this.setUnitTimetable(); + this.setRoomTimetable(); } /** @@ -118,6 +164,18 @@ public void setScore(HardSoftScore score) { this.score = score; } + public void setUnitTimetable() { + for (Unit unit : this.units) { + unit.timetables.add(this); + } + } + + public void setRoomTimetable() { + for (Room room : this.rooms) { + room.timetables.add(this); + } + } + /** * Identify conflicting units having common students at the same time. * diff --git a/backend/src/main/java/org/acme/domain/Unit.java b/backend/src/main/java/org/acme/domain/Unit.java index 8c66e3b..f2cac65 100644 --- a/backend/src/main/java/org/acme/domain/Unit.java +++ b/backend/src/main/java/org/acme/domain/Unit.java @@ -4,15 +4,22 @@ import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OneToOne; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonManagedReference; + /** * Represents a unit. @@ -23,7 +30,10 @@ @PlanningEntity public class Unit extends PanacheEntity { - @OneToMany(mappedBy = "unit", orphanRemoval = false) + // TODO: change unit to be the owner, rather than the student being owner + @JsonIgnoreProperties("units") + @ManyToMany(mappedBy = "units", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @JsonManagedReference public List students; @PlanningId @@ -39,12 +49,25 @@ public class Unit extends PanacheEntity { @PlanningVariable public LocalTime startTime; - @OneToOne(mappedBy = "unit", orphanRemoval = false) + /* + currently each unit only has 1 'slot' on the timetable, so it can only + be associated with one room, but in the final product, we would most + likely have to change this to a many-to-many relationship + i.e. list of Rooms + */ + @JsonIgnoreProperties("units") + @ManyToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "room_id") @PlanningVariable public Room room; public boolean wantsLab; + @JsonIgnoreProperties("units") + @ManyToMany(mappedBy = "units", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @JsonManagedReference + public List timetables = new ArrayList(); + public Unit() { } @@ -61,6 +84,7 @@ public Unit(int unitID, String name, Duration duration, List students) this.name = name; this.duration = duration; this.students = students; + this.setStudentsUnits(); } /** @@ -78,6 +102,7 @@ public Unit(int unitID, String name, Duration duration, List students, this.duration = duration; this.students = students; this.wantsLab = wantsLab; + this.setStudentsUnits(); } public int getUnitID() { @@ -121,6 +146,7 @@ public void setStartTime(LocalTime startTime) { } public LocalTime getEnd() { + if (startTime == null) return null; return startTime.plus(duration); } @@ -130,6 +156,13 @@ public List getStudents() { public void setStudents(List students) { this.students = students; + this.setStudentsUnits();; + } + + public void setStudentsUnits() { + for (Student student : this.students) { + student.units.add(this); + } } /** diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 43e49f3..7d26f06 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -13,4 +13,4 @@ quarkus.datasource.password = ${QUARKUS_DATASOURCE_PASSWORD} quarkus.datasource.jdbc.url = ${QUARKUS_DATASOURCE_JDBC_URL} # drop and create the database at startup (use `update` to only update the schema) -quarkus.hibernate-orm.database.generation = update \ No newline at end of file +quarkus.hibernate-orm.database.generation = create-drop \ No newline at end of file From 051b4fb1b41c637e630f371f586bd302df892ec7 Mon Sep 17 00:00:00 2001 From: Eden Xu <130119691+FlyingPufferFish@users.noreply.github.com> Date: Tue, 24 Sep 2024 07:39:04 +1000 Subject: [PATCH 3/5] feat(database): persist generated timetable to database --- .../main/java/org/acme/TimetableResource.java | 82 ++++++++++++++++++- .../src/main/java/org/acme/domain/Room.java | 8 +- .../main/java/org/acme/domain/Student.java | 2 +- .../main/java/org/acme/domain/Timetable.java | 6 +- .../src/main/java/org/acme/domain/Unit.java | 11 ++- 5 files changed, 100 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/org/acme/TimetableResource.java b/backend/src/main/java/org/acme/TimetableResource.java index 7eb4200..223ea73 100644 --- a/backend/src/main/java/org/acme/TimetableResource.java +++ b/backend/src/main/java/org/acme/TimetableResource.java @@ -16,6 +16,7 @@ import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -70,6 +71,25 @@ public Timetable hello() throws ExecutionException, InterruptedException { Unit u3 = new Unit(3, "3", Duration.ofHours(2), List.of(f, g, h, i), false); Unit u4 = new Unit(4, "4", Duration.ofHours(2), List.of(a, b), false); + // a.persist(); + // b.persist(); + // c.persist(); + // d.persist(); + // e.persist(); + // f.persist(); + // g.persist(); + // h.persist(); + // i.persist(); + + // r1.persist(); + // r2.persist(); + // r3.persist(); + + // u1.persist(); + // u2.persist(); + // u3.persist(); + // u4.persist(); + var problem = new Timetable( List.of( u1, u2, u3, u4 @@ -94,11 +114,71 @@ public Timetable hello() throws ExecutionException, InterruptedException { List.of(r1, r2, r3) ); - problem.persist(); + // problem.persist(); + + // currenttly working + System.out.println("problem created!!!!!!\n\n\n"); + /* + * During this solving phase, any modifications to any Java class that + * extend PanacheEntity, will become detached + * + * i.e. whatever was already stored in the database is not being + * updated when the solver modifies them. When this occurs, the Java + * object in our program goes into 'detached' state + */ Timetable solution = solverManager.solve("job 1", problem).getFinalBestSolution(); + solution.persist(); + // System.out.println("UNITS START: \n\n"); + // List scheduled_units = new ArrayList(); + // for (Unit unit : solution.units) { + // Unit db_unit = Unit.findById(unit.id); + // System.out.print(db_unit.startTime); + // System.out.print(unit.startTime); + // db_unit.dayOfWeek = unit.dayOfWeek; + // db_unit.startTime = unit.startTime; + // System.out.println("\ncheck persistence: "); + // System.out.print(unit.room.isPersistent()); // true + // System.out.print(db_unit.isPersistent()); // true + // db_unit.setRoom(unit.room); + // scheduled_units.add(unit); + // } + // System.out.println("UNITS DONE \n\n"); + + // List scheduled_rooms = new ArrayList(); + // for (Room room : solution.rooms) { + // scheduled_rooms.add(room); + // } + + // Timetable result = new Timetable( + // scheduled_units, + // List.of( + // DayOfWeek.MONDAY, + // DayOfWeek.TUESDAY, + // DayOfWeek.WEDNESDAY + // ), + // List.of( + // LocalTime.of(15, 0) + // ), + // scheduled_rooms + // ); + + // result.persist(); + + + // for (Unit unit : solution.units) { + // System.out.println("unit:\n"); + // System.out.print(unit.startTime); + // } + // problem.persist(); + + // DEBUGGING detached entity solution + // solution.persist(); + // Timetable.getEntityManager().clear(); + // solution = Timetable.getEntityManager().merge(solution); // solution.persist(); + System.out.println("finished!!!!!!"); return solution; } diff --git a/backend/src/main/java/org/acme/domain/Room.java b/backend/src/main/java/org/acme/domain/Room.java index b8ef648..f5403ea 100644 --- a/backend/src/main/java/org/acme/domain/Room.java +++ b/backend/src/main/java/org/acme/domain/Room.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonManagedReference; @@ -32,11 +33,14 @@ public class Room extends PanacheEntity { @JsonIgnoreProperties("room") @OneToMany(mappedBy = "room", orphanRemoval = false) - public List unit; + @JsonManagedReference + @JsonIgnore + public List units = new ArrayList(); @JsonIgnoreProperties("rooms") - @ManyToMany(mappedBy = "rooms", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @ManyToMany(mappedBy = "rooms", fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JsonManagedReference + @JsonIgnore public List timetables = new ArrayList(); public Room() { diff --git a/backend/src/main/java/org/acme/domain/Student.java b/backend/src/main/java/org/acme/domain/Student.java index 146f729..05256e4 100644 --- a/backend/src/main/java/org/acme/domain/Student.java +++ b/backend/src/main/java/org/acme/domain/Student.java @@ -28,7 +28,7 @@ public class Student extends PanacheEntity{ public String name; @JsonIgnoreProperties("students") - @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JoinTable( name = "student_unit", joinColumns = @JoinColumn(name = "student_id"), diff --git a/backend/src/main/java/org/acme/domain/Timetable.java b/backend/src/main/java/org/acme/domain/Timetable.java index 71e44a2..c261671 100644 --- a/backend/src/main/java/org/acme/domain/Timetable.java +++ b/backend/src/main/java/org/acme/domain/Timetable.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonManagedReference; @@ -47,7 +48,7 @@ public class Timetable extends PanacheEntity { * campus, hence the many-to-many relationship */ @JsonIgnoreProperties("timetables") - @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JoinTable( name = "room_timetable", joinColumns = @JoinColumn(name = "timetable_id"), @@ -55,6 +56,7 @@ public class Timetable extends PanacheEntity { ) @JsonManagedReference @ProblemFactCollectionProperty + @JsonIgnore @ValueRangeProvider public List rooms; @@ -64,7 +66,7 @@ public class Timetable extends PanacheEntity { * multiple campuses, so may appear in multiple timetables */ @JsonIgnoreProperties("timetables") - @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JoinTable( name = "unit_timetable", joinColumns = @JoinColumn(name = "timetable_id"), diff --git a/backend/src/main/java/org/acme/domain/Unit.java b/backend/src/main/java/org/acme/domain/Unit.java index f2cac65..e140161 100644 --- a/backend/src/main/java/org/acme/domain/Unit.java +++ b/backend/src/main/java/org/acme/domain/Unit.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonManagedReference; @@ -32,7 +33,7 @@ public class Unit extends PanacheEntity { // TODO: change unit to be the owner, rather than the student being owner @JsonIgnoreProperties("units") - @ManyToMany(mappedBy = "units", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @ManyToMany(mappedBy = "units", fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JsonManagedReference public List students; @@ -56,16 +57,18 @@ public class Unit extends PanacheEntity { i.e. list of Rooms */ @JsonIgnoreProperties("units") - @ManyToOne(cascade = CascadeType.ALL) + @ManyToOne(cascade = {CascadeType.ALL}) @JoinColumn(name = "room_id") + @JsonManagedReference @PlanningVariable public Room room; public boolean wantsLab; @JsonIgnoreProperties("units") - @ManyToMany(mappedBy = "units", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) + @ManyToMany(mappedBy = "units", fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JsonManagedReference + @JsonIgnore public List timetables = new ArrayList(); public Unit() { @@ -180,6 +183,7 @@ public Room getRoom() { public void setRoom(Room room) { this.room = room; + this.room.units.add(this); } public boolean isWantsLab() { @@ -189,4 +193,5 @@ public boolean isWantsLab() { public void setWantsLab(boolean wantsLab) { this.wantsLab = wantsLab; } + } \ No newline at end of file From 970688e2e5fbaea35c2192b8f43bf08ed7a644fc Mon Sep 17 00:00:00 2001 From: Eden Xu <130119691+FlyingPufferFish@users.noreply.github.com> Date: Wed, 25 Sep 2024 18:55:43 +1000 Subject: [PATCH 4/5] feat(database): :card_file_box: setup to allow storage of timetable solutions --- .../main/java/org/acme/TimetableResource.java | 93 +++---------------- .../src/main/java/org/acme/domain/Campus.java | 2 - .../src/main/java/org/acme/domain/Room.java | 6 ++ .../main/java/org/acme/domain/Student.java | 2 + .../src/main/java/org/acme/domain/Unit.java | 20 ++-- 5 files changed, 35 insertions(+), 88 deletions(-) diff --git a/backend/src/main/java/org/acme/TimetableResource.java b/backend/src/main/java/org/acme/TimetableResource.java index 223ea73..db7a49b 100644 --- a/backend/src/main/java/org/acme/TimetableResource.java +++ b/backend/src/main/java/org/acme/TimetableResource.java @@ -16,7 +16,6 @@ import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalTime; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -71,25 +70,6 @@ public Timetable hello() throws ExecutionException, InterruptedException { Unit u3 = new Unit(3, "3", Duration.ofHours(2), List.of(f, g, h, i), false); Unit u4 = new Unit(4, "4", Duration.ofHours(2), List.of(a, b), false); - // a.persist(); - // b.persist(); - // c.persist(); - // d.persist(); - // e.persist(); - // f.persist(); - // g.persist(); - // h.persist(); - // i.persist(); - - // r1.persist(); - // r2.persist(); - // r3.persist(); - - // u1.persist(); - // u2.persist(); - // u3.persist(); - // u4.persist(); - var problem = new Timetable( List.of( u1, u2, u3, u4 @@ -114,71 +94,24 @@ public Timetable hello() throws ExecutionException, InterruptedException { List.of(r1, r2, r3) ); - // problem.persist(); - - // currenttly working - System.out.println("problem created!!!!!!\n\n\n"); - /* - * During this solving phase, any modifications to any Java class that - * extend PanacheEntity, will become detached + * During this solving phase, new Unit objects will be created with the + * alloted date and Room assignment. + * + * Currently, the 'old' Unit objects in the 'problem' variable and the + * 'new' Unit objects in the 'solution' variable are stored as different + * Units in the database due to our inability to control the behaviour + * of solverManager.solve * - * i.e. whatever was already stored in the database is not being - * updated when the solver modifies them. When this occurs, the Java - * object in our program goes into 'detached' state + * i.e. after solving, there will be 2 copies of each Unit in the + * database, where the 'old' Unit has the list of students but no + * timetable assignment, while the 'new' Unit does not have the list + * of students enrolled, but does have the assigned date and room */ Timetable solution = solverManager.solve("job 1", problem).getFinalBestSolution(); - solution.persist(); - // System.out.println("UNITS START: \n\n"); - // List scheduled_units = new ArrayList(); - // for (Unit unit : solution.units) { - // Unit db_unit = Unit.findById(unit.id); - // System.out.print(db_unit.startTime); - // System.out.print(unit.startTime); - // db_unit.dayOfWeek = unit.dayOfWeek; - // db_unit.startTime = unit.startTime; - // System.out.println("\ncheck persistence: "); - // System.out.print(unit.room.isPersistent()); // true - // System.out.print(db_unit.isPersistent()); // true - // db_unit.setRoom(unit.room); - // scheduled_units.add(unit); - // } - // System.out.println("UNITS DONE \n\n"); - - // List scheduled_rooms = new ArrayList(); - // for (Room room : solution.rooms) { - // scheduled_rooms.add(room); - // } - - // Timetable result = new Timetable( - // scheduled_units, - // List.of( - // DayOfWeek.MONDAY, - // DayOfWeek.TUESDAY, - // DayOfWeek.WEDNESDAY - // ), - // List.of( - // LocalTime.of(15, 0) - // ), - // scheduled_rooms - // ); - - // result.persist(); - - - // for (Unit unit : solution.units) { - // System.out.println("unit:\n"); - // System.out.print(unit.startTime); - // } - // problem.persist(); - - // DEBUGGING detached entity solution - // solution.persist(); - // Timetable.getEntityManager().clear(); - // solution = Timetable.getEntityManager().merge(solution); - // solution.persist(); - System.out.println("finished!!!!!!"); + solution.persist(); + // saves the solution timetable and all related entities to database return solution; } diff --git a/backend/src/main/java/org/acme/domain/Campus.java b/backend/src/main/java/org/acme/domain/Campus.java index 365844c..a08f6e1 100644 --- a/backend/src/main/java/org/acme/domain/Campus.java +++ b/backend/src/main/java/org/acme/domain/Campus.java @@ -9,11 +9,9 @@ public class Campus extends PanacheEntity { public String name; - // empty constructor public Campus() { } - // constructor with name input public Campus(String name) { this.name = name; } diff --git a/backend/src/main/java/org/acme/domain/Room.java b/backend/src/main/java/org/acme/domain/Room.java index f5403ea..4ed0f00 100644 --- a/backend/src/main/java/org/acme/domain/Room.java +++ b/backend/src/main/java/org/acme/domain/Room.java @@ -31,12 +31,18 @@ public class Room extends PanacheEntity { public boolean isLab; + /** + * A list of units that are taught in a Room + */ @JsonIgnoreProperties("room") @OneToMany(mappedBy = "room", orphanRemoval = false) @JsonManagedReference @JsonIgnore public List units = new ArrayList(); + /** + * A list of timetables that the Room is a part of + */ @JsonIgnoreProperties("rooms") @ManyToMany(mappedBy = "rooms", fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JsonManagedReference diff --git a/backend/src/main/java/org/acme/domain/Student.java b/backend/src/main/java/org/acme/domain/Student.java index 05256e4..e4f10f7 100644 --- a/backend/src/main/java/org/acme/domain/Student.java +++ b/backend/src/main/java/org/acme/domain/Student.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonManagedReference; @@ -35,6 +36,7 @@ public class Student extends PanacheEntity{ inverseJoinColumns = @JoinColumn(name = "unit_id") ) @JsonManagedReference + @JsonIgnore public List units = new ArrayList(); public Student() { diff --git a/backend/src/main/java/org/acme/domain/Unit.java b/backend/src/main/java/org/acme/domain/Unit.java index e140161..05a4e0f 100644 --- a/backend/src/main/java/org/acme/domain/Unit.java +++ b/backend/src/main/java/org/acme/domain/Unit.java @@ -50,12 +50,13 @@ public class Unit extends PanacheEntity { @PlanningVariable public LocalTime startTime; - /* - currently each unit only has 1 'slot' on the timetable, so it can only - be associated with one room, but in the final product, we would most - likely have to change this to a many-to-many relationship - i.e. list of Rooms - */ + /* + * currently each unit only has 1 'slot' on the timetable, so it can only + * be associated with one room, but in the final product, we would most + * likely have to change this to a many-to-many relationship + * i.e. list of Rooms, because we might want to separate lecture/tutorial + * etc. + */ @JsonIgnoreProperties("units") @ManyToOne(cascade = {CascadeType.ALL}) @JoinColumn(name = "room_id") @@ -65,6 +66,9 @@ public class Unit extends PanacheEntity { public boolean wantsLab; + /* + * The timetables that the Unit object belongs to + */ @JsonIgnoreProperties("units") @ManyToMany(mappedBy = "units", fetch = FetchType.LAZY, cascade = {CascadeType.ALL}) @JsonManagedReference @@ -162,6 +166,10 @@ public void setStudents(List students) { this.setStudentsUnits();; } + /** + * This is to ensure that many-to-many relationship can be properly setup + * in the database + */ public void setStudentsUnits() { for (Student student : this.students) { student.units.add(this); From 3dbaa8efea0135a92b28dddee2cd24b48c734c66 Mon Sep 17 00:00:00 2001 From: Eden Xu <130119691+FlyingPufferFish@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:05:42 +1000 Subject: [PATCH 5/5] refactor(database): store timefold score in database --- backend/src/main/java/org/acme/domain/Timetable.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/main/java/org/acme/domain/Timetable.java b/backend/src/main/java/org/acme/domain/Timetable.java index c261671..cacbc33 100644 --- a/backend/src/main/java/org/acme/domain/Timetable.java +++ b/backend/src/main/java/org/acme/domain/Timetable.java @@ -76,9 +76,8 @@ public class Timetable extends PanacheEntity { @PlanningEntityCollectionProperty public List units; - @Transient @PlanningScore - private HardSoftScore score; + public HardSoftScore score; public Timetable() {