diff --git a/CHANGELOG.md b/CHANGELOG.md index e7716a4141b69299010bf893bb8fe9544d81a1c7..9bd57e816d8f17f31c92e8bbb37099fb257712d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Deprecated + +### Removed + +## [2.1.4] + ### Added + - Students are now provided with a suggestion of their most recent location within a lab when they requeue. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) ### Changed + - Deleting a lab will now show a dialog with a warning. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) ### Fixed + - Requests are now soft-deleted when the corresponding lab is deleted. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) + - Students can no longer enqueue twice. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx) ### Deprecated diff --git a/build.gradle.kts b/build.gradle.kts index ef3e0e3f3f2323a929a46467f6e84cbb51cbac99..2f53b8d1ca9174de7bd7b318547176921f2eb96d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,11 +4,11 @@ import com.diffplug.gradle.spotless.SpotlessExtension import org.springframework.boot.gradle.tasks.run.BootRun group = "nl.tudelft.ewi.queue" -version = "2.1.3" +version = "2.1.4" val javaVersion = JavaVersion.VERSION_17 -val libradorVersion = "1.0.3-SNAPSHOT6" +val libradorVersion = "1.2.2-SNAPSHOT" val labradoorVersion = "1.3.5" val queryDslVersion = "4.4.0" diff --git a/src/main/java/nl/tudelft/queue/cache/AssignmentCacheManager.java b/src/main/java/nl/tudelft/queue/cache/AssignmentCacheManager.java index 5dbe488d4320dbdce903517d2acd743b63183c6b..2641417d860c40d0387ad649a275dbcf409fa6de 100644 --- a/src/main/java/nl/tudelft/queue/cache/AssignmentCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/AssignmentCacheManager.java @@ -21,6 +21,7 @@ import java.util.List; import nl.tudelft.labracore.api.AssignmentControllerApi; import nl.tudelft.labracore.api.dto.AssignmentDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/ClusterCacheManager.java b/src/main/java/nl/tudelft/queue/cache/ClusterCacheManager.java index 84972774ba151f731a23fb103516782ebaef950c..49b8ac772da73dcd3250530a433ec8bfee0f226a 100644 --- a/src/main/java/nl/tudelft/queue/cache/ClusterCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/ClusterCacheManager.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import nl.tudelft.labracore.api.ClusterControllerApi; import nl.tudelft.labracore.api.dto.ClusterDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/CohortCacheManager.java b/src/main/java/nl/tudelft/queue/cache/CohortCacheManager.java index cde48d7f70da921b38b39c42eb3f372892b5a02b..9d01f2470ab9cc74ae6a514fbb9f3f334d6e2240 100644 --- a/src/main/java/nl/tudelft/queue/cache/CohortCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/CohortCacheManager.java @@ -21,6 +21,7 @@ import java.util.List; import nl.tudelft.labracore.api.CohortControllerApi; import nl.tudelft.labracore.api.dto.CohortDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/CoreCacheManager.java b/src/main/java/nl/tudelft/queue/cache/CoreCacheManager.java deleted file mode 100644 index e1e309f37f2a340304aabf576948f99c1c606413..0000000000000000000000000000000000000000 --- a/src/main/java/nl/tudelft/queue/cache/CoreCacheManager.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Queue - A Queueing system that can be used to handle labs in higher education - * Copyright (C) 2016-2021 Delft University of Technology - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ -package nl.tudelft.queue.cache; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -import com.google.common.collect.Sets; - -public abstract class CoreCacheManager<ID, DTO> { - protected final Map<ID, DTO> cache = new ConcurrentHashMap<>(); - protected final Set<ID> empties = new HashSet<>(); - - /** - * Fetches the objects currently missing from the cache. These objects are fetched through a list of their - * IDs. - * - * @param ids The list of IDs of objects to fetch. - * @return The list of fetched objects. - */ - protected abstract List<DTO> fetch(List<ID> ids); - - /** - * Gets the ID of a particular instance of the data object. - * - * @param dto The DTO data object that will be cached. - * @return The ID of the DTO object. - */ - protected abstract ID getId(DTO dto); - - /** - * @return The maximum number of items to be fetched from Labracore in one go. - */ - protected int batchSize() { - return 128; - } - - /** - * Used to manually check the status of the cache and invalidate the cache if necessary. - */ - protected synchronized void validateCache() { - } - - /** - * Gets a list of DTOs from a list of ids. The list of ids gets broken up into already cached items and to - * be fetched items. The to be fetched are then fetched and the already cached are just fetched from - * cache. - * - * @param ids The ids to find matching DTOs for. - * @return The list of DTOs found to be matching. - */ - public List<DTO> get(List<ID> ids) { - validateCache(); - - List<ID> misses = ids.stream() - .filter(id -> !cache.containsKey(id) && !empties.contains(id)) - .collect(Collectors.toList()); - if (!misses.isEmpty()) { - forEachBatch(misses, batch -> registerImpl(new HashSet<>(batch), fetch(batch))); - } - - return ids.stream() - .map(cache::get) - .collect(Collectors.toList()); - } - - /** - * Gets a list of DTOs from a stream of IDs. This method is mostly used as a convenience method wrapping - * {@link #get(List)}. - * - * @param ids The stream of ids to be collected and requested from the cache. - * @return The list of DTOs with the given IDs. - */ - public List<DTO> get(Stream<ID> ids) { - return get(ids.collect(Collectors.toList())); - } - - /** - * Gets a single DTO from its id. If the DTO is already cached, no external lookup is done. - * - * @param id The id of the DTO to lookup. - * @return The requested DTO if it exists, otherwise nothing. - */ - public Optional<DTO> get(ID id) { - if (id == null) { - return Optional.empty(); - } - return Optional.of(get(List.of(id))) - .filter(l -> !l.isEmpty()) - .map(l -> l.get(0)); - } - - /** - * Gets the DTO with the given id or throws an exception with status code 404 if none such DTO could be - * found. - * - * @param id The id of the DTO to find. - * @return The found DTO with the given id. - */ - public DTO getOrThrow(ID id) { - var l = get(List.of(id)); - if (l == null || l.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find DTO by id " + id); - } - return l.get(0); - } - - /** - * Registers an entry into the cache after validating the cache first. - * - * @param dto The DTO that is to be registered. - */ - public void register(DTO dto) { - validateCache(); - registerImpl(dto); - } - - /** - * Registers a list of entries into the cache after validating the cache first. - * - * @param dtos The DTOs that are to be registered. - */ - public void register(List<DTO> dtos) { - validateCache(); - registerImpl(dtos); - } - - /** - * Implementation of the register method. Registers an entry into the cache. - * - * @param dto The DTO that is to be registered. - */ - private void registerImpl(DTO dto) { - cache.put(getId(dto), dto); - registerAdditionally(dto); - } - - /** - * Implementation of the register method. Registers a list of entries into the cache. - * - * @param dtos The DTOs that are to be registered. - */ - private void registerImpl(List<DTO> dtos) { - dtos.forEach(this::registerImpl); - } - - /** - * Implementation of the register method. Registers a list of entries into the cache and also registers - * the IDs that were requested but not returned as 'empty' IDs. - * - * @param requests The set of requested IDs. - * @param dtos The list of DTOs returned from the request. - */ - private void registerImpl(Set<ID> requests, List<DTO> dtos) { - dtos.forEach(this::registerImpl); - empties.addAll(Sets.difference(requests, - dtos.stream().map(this::getId).collect(Collectors.toSet()))); - } - - /** - * Performs additional registers in other caches if necessary. - * - * @param dto The DTO that is being registered. - */ - protected void registerAdditionally(DTO dto) { - } - - /** - * Helper function for iterating over batches of items. The batch size is determined through the - * {@link #batchSize()} method in implementations of this class. Each created batch will be passed on to - * the consumer function f. - * - * @param ids The list of ids that are to be batched. - * @param f The consumer of each batch. - */ - private void forEachBatch(List<ID> ids, Consumer<List<ID>> f) { - List<ID> batch = new ArrayList<>(); - for (ID id : ids) { - batch.add(id); - - if (batch.size() >= batchSize()) { - f.accept(batch); - batch.clear(); - } - } - - if (!batch.isEmpty()) { - f.accept(batch); - } - } - -} diff --git a/src/main/java/nl/tudelft/queue/cache/CourseCacheManager.java b/src/main/java/nl/tudelft/queue/cache/CourseCacheManager.java index a841a89a44e23436f095b1e5b1fa29be257a2b5b..b88979fb006d9c0e0afa91f2b7cd2de46ce347ff 100644 --- a/src/main/java/nl/tudelft/queue/cache/CourseCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/CourseCacheManager.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import nl.tudelft.labracore.api.CourseControllerApi; import nl.tudelft.labracore.api.dto.CourseDetailsDTO; import nl.tudelft.labracore.api.dto.CourseSummaryDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/EditionCacheManager.java b/src/main/java/nl/tudelft/queue/cache/EditionCacheManager.java index b1ef979159d7339a4b8bcfedef4207692bc3fe84..1f086bafa4d5f6af6b7f90fcff4657d786b48122 100644 --- a/src/main/java/nl/tudelft/queue/cache/EditionCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/EditionCacheManager.java @@ -21,6 +21,7 @@ import java.util.List; import nl.tudelft.labracore.api.EditionControllerApi; import nl.tudelft.labracore.api.dto.EditionDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/EditionCollectionCacheManager.java b/src/main/java/nl/tudelft/queue/cache/EditionCollectionCacheManager.java index c1c37e0646697e80de01053b3ef7346c89b296a4..b65cb9068baf896900303b5c428170b9713585b1 100644 --- a/src/main/java/nl/tudelft/queue/cache/EditionCollectionCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/EditionCollectionCacheManager.java @@ -21,6 +21,7 @@ import java.util.List; import nl.tudelft.labracore.api.EditionCollectionControllerApi; import nl.tudelft.labracore.api.dto.EditionCollectionDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/EditionRolesCacheManager.java b/src/main/java/nl/tudelft/queue/cache/EditionRolesCacheManager.java index ce9ebe998c178569cc96ed7e54e0bbeaf6b3062c..4d040be8ef580611d14e025c7c119ddf55ad8b6f 100644 --- a/src/main/java/nl/tudelft/queue/cache/EditionRolesCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/EditionRolesCacheManager.java @@ -24,6 +24,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import nl.tudelft.labracore.api.EditionControllerApi; import nl.tudelft.labracore.api.dto.RolePersonDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/ModuleCacheManager.java b/src/main/java/nl/tudelft/queue/cache/ModuleCacheManager.java index 3ab0cf2e4fd1ad0b0bd05a719a95396ff6bd7f81..13905ea2c81b482f3bd7a1067f8ea9f77cdbf0c5 100644 --- a/src/main/java/nl/tudelft/queue/cache/ModuleCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/ModuleCacheManager.java @@ -21,6 +21,7 @@ import java.util.List; import nl.tudelft.labracore.api.ModuleControllerApi; import nl.tudelft.labracore.api.dto.ModuleDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/PersonCacheManager.java b/src/main/java/nl/tudelft/queue/cache/PersonCacheManager.java index 476877f9846c3403a50f8da52fc2ee42719573a9..85c3587b3e7e41d7180982f805688cfeb1eb1334 100644 --- a/src/main/java/nl/tudelft/queue/cache/PersonCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/PersonCacheManager.java @@ -21,6 +21,7 @@ import java.util.List; import nl.tudelft.labracore.api.PersonControllerApi; import nl.tudelft.labracore.api.dto.PersonSummaryDTO; +import nl.tudelft.librador.cache.TimedCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/RoleCacheManager.java b/src/main/java/nl/tudelft/queue/cache/RoleCacheManager.java index 277de71911c1c2bfc82d674407dd5461fc89d7b1..a6f6a4c7782a55c627db6816c1236517c15ba9d0 100644 --- a/src/main/java/nl/tudelft/queue/cache/RoleCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/RoleCacheManager.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import nl.tudelft.labracore.api.RoleControllerApi; import nl.tudelft.labracore.api.dto.Id; import nl.tudelft.labracore.api.dto.RoleDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/RoomCacheManager.java b/src/main/java/nl/tudelft/queue/cache/RoomCacheManager.java index 6e4765c41a6808ae748bf910b3e1201225bbff2d..a98e225bb8a02b1cc8f3b39c40ed889b1821d3f5 100644 --- a/src/main/java/nl/tudelft/queue/cache/RoomCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/RoomCacheManager.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import nl.tudelft.labracore.api.RoomControllerApi; import nl.tudelft.labracore.api.dto.RoomDetailsDTO; import nl.tudelft.labracore.api.dto.RoomSummaryDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/SessionCacheManager.java b/src/main/java/nl/tudelft/queue/cache/SessionCacheManager.java index d6ca827405103f8a97d708f4780afbc512748628..a6654f49ee8c44faf13eb0a7bcbfeb92c9da9d4f 100644 --- a/src/main/java/nl/tudelft/queue/cache/SessionCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/SessionCacheManager.java @@ -23,6 +23,7 @@ import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import nl.tudelft.labracore.api.SessionControllerApi; import nl.tudelft.labracore.api.dto.SessionDetailsDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/nl/tudelft/queue/cache/StudentGroupCacheManager.java b/src/main/java/nl/tudelft/queue/cache/StudentGroupCacheManager.java index 286f15067d80c7ca1a45d5f23d909926c33e5e3a..8badc3273692afa578d4873723b0b13a79f29857 100644 --- a/src/main/java/nl/tudelft/queue/cache/StudentGroupCacheManager.java +++ b/src/main/java/nl/tudelft/queue/cache/StudentGroupCacheManager.java @@ -29,6 +29,7 @@ import nl.tudelft.labracore.api.StudentGroupControllerApi; import nl.tudelft.labracore.api.dto.RolePersonLayer1DTO; import nl.tudelft.labracore.api.dto.StudentGroupDetailsDTO; import nl.tudelft.labracore.api.dto.StudentGroupSummaryDTO; +import nl.tudelft.librador.cache.CoreCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.Nullable; diff --git a/src/main/java/nl/tudelft/queue/cache/TimedCacheManager.java b/src/main/java/nl/tudelft/queue/cache/TimedCacheManager.java deleted file mode 100644 index c4fd1645d4ebafa1878e150ea0422f10527c9801..0000000000000000000000000000000000000000 --- a/src/main/java/nl/tudelft/queue/cache/TimedCacheManager.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Queue - A Queueing system that can be used to handle labs in higher education - * Copyright (C) 2016-2021 Delft University of Technology - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ -package nl.tudelft.queue.cache; - -public abstract class TimedCacheManager<ID, DTO> extends CoreCacheManager<ID, DTO> { - /** - * The default timeout of the timed cache. - */ - public static final long DEFAULT_TIMEOUT = 60000L; - - /** - * The set timeout of the timed cache in milliseconds. This many milliseconds after the last cache - * invalidation, this cache is invalidated again. - */ - private final long timeout; - - /** - * The last time in milliseconds since epoch (system-time) that this cache was (in)validated. - * {@link #timeout} milliseconds after this timestamp, the cache is invalidated (again). - */ - private long lastValidation = System.currentTimeMillis(); - - /** - * Constructs a new timed cache with the timeout set to {@link #DEFAULT_TIMEOUT}. - */ - public TimedCacheManager() { - this.timeout = DEFAULT_TIMEOUT; - } - - /** - * Constructs a new timed cache with the timeout set to the given timeout. - * - * @param timeout The timeout to set internally. - */ - public TimedCacheManager(long timeout) { - this.timeout = timeout; - } - - @Override - protected synchronized void validateCache() { - long now = System.currentTimeMillis(); - if (lastValidation + timeout < now) { - cache.clear(); - empties.clear(); - lastValidation = now; - } - } -} diff --git a/src/main/java/nl/tudelft/queue/controller/AdminController.java b/src/main/java/nl/tudelft/queue/controller/AdminController.java index ae26344f00e4d0ad1cc30838894d790963027463..775c54388f7d846a7b15782072ed397114c3bb16 100644 --- a/src/main/java/nl/tudelft/queue/controller/AdminController.java +++ b/src/main/java/nl/tudelft/queue/controller/AdminController.java @@ -129,11 +129,11 @@ public class AdminController { @GetMapping("/running") public String getRunningLabs(Model model) { LocalDateTime now = LocalDateTime.now(); - var runningEditions = eCache.get(Objects.requireNonNull( + var runningEditions = eCache.getAndIgnoreMissing(Objects.requireNonNull( eApi.getAllEditionsActiveAtDate(LocalDateTime.now()) .map(EditionSummaryDTO::getId).collectList().block())); - var runningSessions = sCache.get(runningEditions.stream() + var runningSessions = sCache.getAndIgnoreMissing(runningEditions.stream() .flatMap(e -> e.getSessions().stream()) .filter(s -> s.getEnd().isAfter(now)) .map(SessionSummaryDTO::getId)); diff --git a/src/main/java/nl/tudelft/queue/controller/AssignmentController.java b/src/main/java/nl/tudelft/queue/controller/AssignmentController.java index d66572f5124aaeecd3ce15928a1d84b6452f967a..7f1b31112707b53239ee7d09581cedd59e6b9d12 100644 --- a/src/main/java/nl/tudelft/queue/controller/AssignmentController.java +++ b/src/main/java/nl/tudelft/queue/controller/AssignmentController.java @@ -82,6 +82,9 @@ public class AssignmentController { public String createAssignment(@PathVariable Long mId, QueueAssignmentCreateDTO dto, Model model) { + + ModuleDetailsDTO module = mCache.getRequired(mId); + dto.setModuleId(mId); AssignmentCreateDTO create = dto.apply(); @@ -91,7 +94,6 @@ public class AssignmentController { aApi.addAssignment(create).block(); - ModuleDetailsDTO module = mCache.getOrThrow(mId); return "redirect:/edition/" + module.getEdition().getId() + "/modules"; } @@ -107,13 +109,14 @@ public class AssignmentController { @PreAuthorize("@permissionService.canManageAssignment(#assignmentId)") public String getAssignmentRemovePage(@PathVariable Long assignmentId, Model model) { - var assignment = aCache.getOrThrow(assignmentId); - var module = mCache.getOrThrow(assignment.getModule().getId()); - var edition = eCache.getOrThrow(module.getEdition().getId()); + var assignment = aCache.getRequired(assignmentId); + var module = mCache.getRequired(assignment.getModule().getId()); + var edition = eCache.getRequired(module.getEdition().getId()); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("assignment", assignment); @@ -130,8 +133,8 @@ public class AssignmentController { @PostMapping("/assignment/{assignmentId}/remove") @PreAuthorize("@permissionService.canManageAssignment(#assignmentId)") public String removeAssignment(@PathVariable Long assignmentId) { - var assignment = aCache.getOrThrow(assignmentId); - var module = mCache.getOrThrow(assignment.getModule().getId()); + var assignment = aCache.getRequired(assignmentId); + var module = mCache.getRequired(assignment.getModule().getId()); aApi.deleteAssignment(assignmentId).block(); @@ -148,12 +151,13 @@ public class AssignmentController { */ private String addCreateAssignmentAttributes(Long moduleId, QueueAssignmentCreateDTO dto, Model model) { - ModuleDetailsDTO module = mCache.getOrThrow(moduleId); - EditionDetailsDTO edition = eCache.getOrThrow(module.getEdition().getId()); + ModuleDetailsDTO module = mCache.getRequired(moduleId); + EditionDetailsDTO edition = eCache.getRequired(module.getEdition().getId()); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("_module", module); diff --git a/src/main/java/nl/tudelft/queue/controller/EditionController.java b/src/main/java/nl/tudelft/queue/controller/EditionController.java index 3ffd7223c069abd59ee3e2706c94337fc86690d6..f370d8bbd8d785afc17382fe0d1912a023b35d2b 100644 --- a/src/main/java/nl/tudelft/queue/controller/EditionController.java +++ b/src/main/java/nl/tudelft/queue/controller/EditionController.java @@ -48,13 +48,10 @@ import nl.tudelft.queue.model.LabRequest; import nl.tudelft.queue.model.enums.QueueSessionType; import nl.tudelft.queue.model.labs.Lab; import nl.tudelft.queue.repository.QueueSessionRepository; -import nl.tudelft.queue.service.EditionService; -import nl.tudelft.queue.service.EditionStatusService; -import nl.tudelft.queue.service.PermissionService; -import nl.tudelft.queue.service.QuestionService; -import nl.tudelft.queue.service.RoleDTOService; +import nl.tudelft.queue.service.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.core.io.Resource; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -141,6 +138,10 @@ public class EditionController { @Autowired private QuestionService qs; + @Autowired + @Lazy + private LabService ls; + /** * Gets the page listing all editions. This page incorporates pageables to index many different editions * and keep the overview small. @@ -164,7 +165,7 @@ public class EditionController { .block(); eCache.register(editions.getContent()); - erCache.get(editions.getContent().stream().map(EditionDetailsDTO::getId)); + erCache.getAndIgnoreMissing(editions.getContent().stream().map(EditionDetailsDTO::getId)); model.addAttribute("editions", new PageImpl<>(editions.getContent(), pageable, editions.getTotalElements())); @@ -224,7 +225,7 @@ public class EditionController { if (!ps.canEnrolForEdition(editionId)) { return "redirect:/editions"; } - model.addAttribute("edition", eCache.getOrThrow(editionId)); + model.addAttribute("edition", eCache.getRequired(editionId)); return "edition/enrol"; } @@ -256,11 +257,12 @@ public class EditionController { */ @GetMapping("/edition/{editionId}") public String getEditionView(@PathVariable Long editionId, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); return "edition/view/info"; @@ -279,7 +281,7 @@ public class EditionController { public String getEditionParticipantsView(@PathVariable Long editionId, Model model, @RequestParam(value = "student-search", required = false) String studentSearch, @PageableDefault(size = 25) Pageable pageable) { - var edition = eCache.getOrThrow(editionId); + var edition = eCache.getRequired(editionId); var students = roleDTOService.students(edition); if (studentSearch != null) { @@ -288,7 +290,8 @@ public class EditionController { model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("students", toPage(pageable, students)); @@ -307,8 +310,8 @@ public class EditionController { @GetMapping("/edition/{editionId}/modules") @PreAuthorize("@permissionService.canViewEdition(#editionId)") public String getEditionModulesView(@PathVariable Long editionId, Model model) { - var edition = eCache.getOrThrow(editionId); - var modules = mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)); + var edition = eCache.getRequired(editionId); + var modules = mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)); modules.sort(Comparator.comparing(ModuleDetailsDTO::getName)); modules.forEach(m -> { @@ -316,7 +319,7 @@ public class EditionController { m.getAssignments().sort(Comparator.comparing(AssignmentSummaryDTO::getName)); }); - sgCache.get(modules.stream() + sgCache.getAndIgnoreMissing(modules.stream() .flatMap(m -> m.getGroups().stream().map(StudentGroupSmallSummaryDTO::getId))); model.addAttribute("edition", edition); @@ -325,7 +328,8 @@ public class EditionController { modules.stream().flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("groups", modules.stream() .collect(Collectors.toMap(ModuleDetailsDTO::getId, - m -> sgCache.get(m.getGroups().stream().map(StudentGroupSmallSummaryDTO::getId))))); + m -> sgCache.getAndIgnoreMissing( + m.getGroups().stream().map(StudentGroupSmallSummaryDTO::getId))))); return "edition/view/modules"; } @@ -345,11 +349,11 @@ public class EditionController { @RequestParam(required = false, defaultValue = "") List<QueueSessionType> queueSessionTypes, @RequestParam(required = false, defaultValue = "") List<Long> modules, Model model) { - var edition = eCache.getOrThrow(editionId); + var edition = eCache.getRequired(editionId); // Make sure that session details are cached. - var sessions = sCache.get(edition.getSessions().stream() - .map(SessionSummaryDTO::getId)); + var sessions = sCache.getAndHandleAll(edition.getSessions().stream() + .map(SessionSummaryDTO::getId), ls.deleteSessionsByIds()); // Find all labs from the sessions, convert to summaries, and filter. var labs = es.filterLabs( @@ -366,7 +370,8 @@ public class EditionController { model.addAttribute("allModules", edition.getModules()); model.addAttribute("modules", modules); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); return "edition/view/labs"; @@ -382,11 +387,12 @@ public class EditionController { @GetMapping("/edition/{editionId}/questions") @PreAuthorize("@permissionService.canManageQuestions(#editionId)") public String getEditionQuestions(@PathVariable Long editionId, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("questions", qApi.getQuestionsByEdition(editionId).collectList().block()); @@ -403,11 +409,12 @@ public class EditionController { @GetMapping("/edition/{editionId}/participants/create") @PreAuthorize("@permissionService.canManageParticipants(#editionId)") public String getAddParticipantPage(@PathVariable Long editionId, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("role", new QueueRoleCreateDTO(editionId)); @@ -430,11 +437,12 @@ public class EditionController { RoleCreateDTO create = dto.apply(); if (dto.hasErrors()) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("role", dto); @@ -470,7 +478,7 @@ public class EditionController { public String importParticipants(@PathVariable Long editionId, @RequestParam("file") MultipartFile csv, RedirectAttributes attributes) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); try { es.addCourseParticipants(csv, edition); @@ -495,13 +503,14 @@ public class EditionController { @PreAuthorize("@permissionService.canManageParticipants(#editionId)") public String getRemoveParticipantPage(@PathVariable Long editionId, @PathVariable Long personId, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); - model.addAttribute("role", rCache.getOrThrow(new Id() + model.addAttribute("role", rCache.getRequired(new Id() .personId(personId).editionId(editionId))); return "edition/remove/participant"; @@ -548,10 +557,11 @@ public class EditionController { @PreAuthorize("@permissionService.canLeaveEdition(#editionId)") public String getParticipantLeavePage(@PathVariable Long editionId, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); return "edition/view/leave"; @@ -586,12 +596,14 @@ public class EditionController { @PreAuthorize("@permissionService.canViewEditionStatus(#editionId)") public String status(@PathVariable Long editionId, Model model) { - var edition = eCache.getOrThrow(editionId); - var sessions = sCache.get(edition.getSessions().stream().map(SessionSummaryDTO::getId)); + var edition = eCache.getRequired(editionId); + var sessions = sCache.getAndHandleAll(edition.getSessions().stream().map(SessionSummaryDTO::getId), + ls.deleteSessionsByIds()); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("activeLabs", getLabSummariesFromSessions( @@ -599,9 +611,9 @@ public class EditionController { model.addAttribute("inactiveLabs", getLabSummariesFromSessions( sessions, s -> !(s.getStart().isBefore(now()) && now().isBefore(s.getEnd())))); - model.addAttribute("assignments", aCache.get(sessions.stream() + model.addAttribute("assignments", aCache.getAndIgnoreMissing(sessions.stream() .flatMap(s -> s.getAssignments().stream().map(AssignmentSummaryDTO::getId)).distinct())); - model.addAttribute("rooms", rmCache.get(sessions.stream() + model.addAttribute("rooms", rmCache.getAndIgnoreMissing(sessions.stream() .flatMap(s -> s.getRooms().stream().map(RoomDetailsDTO::getId)).distinct())); return "edition/view/status"; @@ -704,11 +716,12 @@ public class EditionController { @PreAuthorize("@permissionService.canManageQuestions(#editionId)") public String getEditQuestionPage(@PathVariable Long editionId, @PathVariable Long questionId, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("question", qApi.getQuestionById(questionId).block()); model.addAttribute("assignments", @@ -749,11 +762,12 @@ public class EditionController { @PreAuthorize("@permissionService.canManageQuestions(#editionId)") public String getDeleteQuestionPage(@PathVariable Long editionId, @PathVariable Long questionId, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("assignments", - mCache.get(edition.getModules().stream().map(ModuleSummaryDTO::getId)).stream() + mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) + .stream() .flatMap(m -> m.getAssignments().stream()).toList()); model.addAttribute("question", qApi.getQuestionById(questionId).block()); diff --git a/src/main/java/nl/tudelft/queue/controller/HistoryController.java b/src/main/java/nl/tudelft/queue/controller/HistoryController.java index 2dbfaf9b23370602199206c63d3bfe9a8db09481..49c985066eaa72a7d01cef2fff8a24e367da7f20 100644 --- a/src/main/java/nl/tudelft/queue/controller/HistoryController.java +++ b/src/main/java/nl/tudelft/queue/controller/HistoryController.java @@ -20,6 +20,8 @@ package nl.tudelft.queue.controller; import java.util.List; import java.util.stream.Collectors; +import javax.transaction.Transactional; + import nl.tudelft.labracore.api.PersonControllerApi; import nl.tudelft.labracore.api.StudentGroupControllerApi; import nl.tudelft.labracore.api.dto.EditionDetailsDTO; @@ -34,6 +36,7 @@ import nl.tudelft.queue.cache.PersonCacheManager; import nl.tudelft.queue.cache.SessionCacheManager; import nl.tudelft.queue.repository.LabRequestRepository; import nl.tudelft.queue.repository.QueueSessionRepository; +import nl.tudelft.queue.service.LabService; import nl.tudelft.queue.service.RequestTableService; import org.springframework.beans.factory.annotation.Autowired; @@ -76,6 +79,9 @@ public class HistoryController { @Autowired private RequestTableService rts; + @Autowired + private LabService ls; + /** * Gets the view representing the request history of the currently authenticated student. * @@ -111,14 +117,14 @@ public class HistoryController { @PathVariable Long editionId, @PathVariable Long studentId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC, size = 25) Pageable pageable) { - var edition = eCache.getOrThrow(editionId); + var edition = eCache.getRequired(editionId); List<Long> groups = sgApi.getGroupsForPersonAndCourse(studentId, edition.getCourse().getId()) .map(StudentGroupSummaryDTO::getId) .collectList().block(); model.addAttribute("edition", edition); - model.addAttribute("student", pCache.getOrThrow(studentId)); + model.addAttribute("student", pCache.getRequired(studentId)); setHistoryModel("/history/edition/" + editionId + "/student/" + studentId, model, studentId, groups, pageable); @@ -136,18 +142,21 @@ public class HistoryController { * @param groupIds The ids of the groups to find requests for. * @param pageable The pageable containing the information on the page to show. */ - private void setHistoryModel(String key, Model model, Long studentId, List<Long> groupIds, + @Transactional + public void setHistoryModel(String key, Model model, Long studentId, List<Long> groupIds, Pageable pageable) { - var editions = eCache.get(pApi.getRolesForPerson(studentId) + var editions = eCache.getAndIgnoreMissing(pApi.getRolesForPerson(studentId) .filter(r -> r.getType() == RoleEditionDetailsDTO.TypeEnum.STUDENT) .map(r -> r.getEdition().getId()) .toStream()); - erCache.get(editions.stream().map(EditionDetailsDTO::getId)); + + erCache.getAndIgnoreMissing(editions.stream().map(EditionDetailsDTO::getId)); var sessionIds = editions.stream() .flatMap(e -> e.getSessions().stream().map(SessionSummaryDTO::getId)) .collect(Collectors.toList()); - sCache.get(sessionIds); + + sCache.getAndHandleAll(sessionIds, ls.deleteSessionsByIds()); var labs = qsr.findAllBySessions(sessionIds); diff --git a/src/main/java/nl/tudelft/queue/controller/HomeController.java b/src/main/java/nl/tudelft/queue/controller/HomeController.java index 16cdd161ff245ba5faeb478e9f3dd7c5ffbed088..20766ace181932bc2e824bee04a908a51916fc96 100644 --- a/src/main/java/nl/tudelft/queue/controller/HomeController.java +++ b/src/main/java/nl/tudelft/queue/controller/HomeController.java @@ -171,17 +171,17 @@ public class HomeController { Set.of(TEACHER_RO).contains(role.getType())) .collect(Collectors.toList()); - var editions = eCache.get(roles.stream().map(r -> r.getEdition().getId())) + var editions = eCache.getAndIgnoreMissing(roles.stream().map(r -> r.getEdition().getId())) .stream() .collect(Collectors.toMap(EditionDetailsDTO::getId, Function.identity())); - Set<SessionDetailsDTO> sessions = new HashSet<>(sCache.get(editions.values().stream() + Set<SessionDetailsDTO> sessions = new HashSet<>(sCache.getAndHandleAll(editions.values().stream() .flatMap(e -> e.getSessions().stream()) .filter(s -> s.getEnd().isAfter(now.minusMonths(1))) - .map(SessionSummaryDTO::getId))); + .map(SessionSummaryDTO::getId), missingIds -> ls.deleteSessionsByIds())); var sharedEditions = editions.values().stream().map(EditionDetailsDTO::getEditionCollections) - .flatMap(List::stream).map(e -> ecCache.getOrThrow(e.getId())) + .flatMap(List::stream).map(e -> ecCache.getRequired(e.getId())) .collect(Collectors.toSet()); var calendarEntries = ls.convertToCalendarEntries( @@ -198,7 +198,8 @@ public class HomeController { var sharedLabs = sharedEditions.stream().collect(Collectors.toMap( EditionCollectionDetailsDTO::getId, s -> es.sortLabs(View.convert(new ArrayList<>(lr.findAllBySessions( - eCache.get(s.getEditions().stream().map(EditionSummaryDTO::getId).toList()) + eCache.getAndIgnoreMissing( + s.getEditions().stream().map(EditionSummaryDTO::getId).toList()) .stream().map(EditionDetailsDTO::getSessions).flatMap(List::stream) .map(SessionSummaryDTO::getId).collect(Collectors.toList()))), QueueSessionSummaryDTO.class)).stream() @@ -207,7 +208,7 @@ public class HomeController { .collect(Collectors.toList()))); if (pd.getDefaultRole() == ADMIN || pd.getDefaultRole() == TEACHER) { - model.addAttribute("allEditions", eCache.get( + model.addAttribute("allEditions", eCache.getAndIgnoreMissing( Objects.requireNonNullElse( eApi.getAllEditionsActiveDuringPeriod( new Period().start(LocalDateTime.now()) @@ -280,7 +281,7 @@ public class HomeController { * @param pageable The pageable containing information on how much feedback needs to be shown. */ private void fillInFeedbackModel(Long assistantId, Person person, Model model, Pageable pageable) { - var assistant = pCache.getOrThrow(assistantId); + var assistant = pCache.getRequired(assistantId); Page<Feedback> feedback = assistantId.equals(person.getId()) ? fr.findByAssistantAnonymised(assistantId, pageable) diff --git a/src/main/java/nl/tudelft/queue/controller/LabController.java b/src/main/java/nl/tudelft/queue/controller/LabController.java index 259078aa0400b063860fbce1380d299ead2b5b2d..773c15859e3dacef9e774fd926798cc6f54b6895 100644 --- a/src/main/java/nl/tudelft/queue/controller/LabController.java +++ b/src/main/java/nl/tudelft/queue/controller/LabController.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; import javax.transaction.Transactional; import nl.tudelft.labracore.api.dto.*; @@ -119,6 +120,9 @@ public class LabController { @Autowired private RoleDTOService roleService; + @Autowired + private HttpSession session; + /** * Gets the page with information on the lab with the given id. This page is different for TAs than for * students. For students, this page shows their history of requests and their current requests. For TAs, @@ -165,7 +169,7 @@ public class LabController { .ifPresent(r -> model.addAttribute("selectionResult", r.toViewDTO())); } - model.addAttribute("modules", mCache.get(qSession.getModules().stream())); + model.addAttribute("modules", mCache.getAndIgnoreMissing(qSession.getModules().stream())); return "lab/view/" + qSession.getType().name().toLowerCase(); } @@ -209,6 +213,10 @@ public class LabController { model.addAttribute("request", new LabRequestCreateDTO()); model.addAttribute("rType", "lab"); + if (session.getAttribute("lastKnownLocation") != null) { + model.addAttribute("lastKnownLocation", session.getAttribute("lastKnownLocation")); + } + return "lab/enqueue/lab"; } else { model.addAttribute("request", new SelectionRequestCreateDTO()); @@ -233,7 +241,7 @@ public class LabController { Model model) { setEnqueuePageAttributes(lab, model, person); - model.addAttribute("assignment", aCache.getOrThrow(assignment)); + model.addAttribute("assignment", aCache.getRequired(assignment)); return "lab/question"; } @@ -254,7 +262,7 @@ public class LabController { Model model) { setEnqueuePageAttributes(lab, model, person); - model.addAttribute("assignment", aCache.getOrThrow(assignment)); + model.addAttribute("assignment", aCache.getRequired(assignment)); model.addAttribute("question", question); model.addAttribute("request", new LabRequestCreateDTO()); model.addAttribute("rType", "lab"); @@ -284,6 +292,11 @@ public class LabController { dto.setSession(lab); rs.createRequest(dto, student.getId(), true); + + if (dto.getComment() != null) { + session.setAttribute("lastKnownLocation", dto.getComment()); + } + return "redirect:/lab/" + lab.getId(); } @@ -357,7 +370,7 @@ public class LabController { public String getLabCreateView(@PathVariable Long editionId, @RequestParam QueueSessionType type, Model model) { model.addAttribute("dto", type.newCreateDto()); - model.addAttribute("edition", eCache.getOrThrow(editionId)); + model.addAttribute("edition", eCache.getRequired(editionId)); model.addAttribute("lType", type); setSessionEditingPageAttributes(List.of(editionId), model); @@ -377,7 +390,7 @@ public class LabController { @PreAuthorize("@permissionService.canManageSharedSessions(#editionCollectionId)") public String getSharedLabCreateView(@PathVariable Long editionCollectionId, @RequestParam QueueSessionType type, Model model) { - var editionCollection = ecCache.getOrThrow(editionCollectionId); + var editionCollection = ecCache.getRequired(editionCollectionId); model.addAttribute("dto", type.newCreateDto()); model.addAttribute("ec", editionCollection); @@ -502,25 +515,7 @@ public class LabController { } /** - * Gets the confirmation view for deleting a single lab. - * - * @param qSession The lab to delete. - * @param model The model to fill out for Thymeleaf template resolution. - * @return The Thymeleaf template to resolve. - */ - @GetMapping("/lab/{qSession}/remove") - @PreAuthorize("@permissionService.canManageSession(#qSession)") - public String getSessionDeleteView(@PathEntity QueueSession<?> qSession, - Model model) { - model.addAttribute("qSession", qSession); - ls.setOrganizationInModel(qSession, model); - - return "lab/remove"; - } - - /** - * Handles a POST request to the session delete endpoint by setting the deleted at date for the given - * session. + * Handles a GET request to the session delete endpoint by soft-deleting the session. * * @param qSession The session to delete. * @return A redirect back to the edition lab overview. @@ -528,8 +523,8 @@ public class LabController { @PostMapping("/lab/{qSession}/remove") @PreAuthorize("@permissionService.canManageSession(#qSession)") public String deleteSession(@PathEntity QueueSession<?> qSession) { + var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession)); ls.deleteSession(qSession); - var session = sCache.getOrThrow(qSession.getSession()); if (session.getEditionCollection() != null) { return "redirect:/shared-edition/" + session.getEditionCollection().getId(); @@ -550,7 +545,7 @@ public class LabController { @PreAuthorize("@permissionService.canManageSession(#qSession)") public String getLabCopyView(@PathEntity QueueSession<?> qSession, Model model) { - var session = sCache.getOrThrow(qSession.getSession()); + var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession)); model.addAttribute("dto", qSession.copyLabCreateDTO(session)); model.addAttribute("lType", qSession.getType()); @@ -688,8 +683,9 @@ public class LabController { * @param model The model to fill using edition and lab information. * @param person The person used to fill in the information. */ - private void setEnqueuePageAttributes(QueueSession<?> qSession, Model model, Person person) { - var session = sCache.getOrThrow(qSession.getSession()); + @Transactional + public void setEnqueuePageAttributes(QueueSession<?> qSession, Model model, Person person) { + var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession)); session.getRooms().sort(Comparator.comparing(r -> r.getBuilding().getName() + r.getName())); model.addAttribute("qSession", qSession.toViewDTO()); @@ -701,26 +697,29 @@ public class LabController { var lab = (Lab) qSession; var isStaffInAll = session.getEditions().stream().allMatch(e -> roleService .rolesForPersonInEdition(e, person).stream().allMatch(roleService::isStaff)); - List<AssignmentDetailsDTO> allowedAssignments = aCache.get(lab.getAllowedRequests().stream() - .map(AllowedRequest::getAssignment).distinct().collect(Collectors.toList())) + List<AssignmentDetailsDTO> allowedAssignments = aCache + .getAndHandleAll(lab.getAllowedRequests().stream() + .map(AllowedRequest::getAssignment).distinct().collect(Collectors.toList()), + assignmentIds -> lab.getAllowedRequests() + .removeIf(a -> assignmentIds.contains(a.getAssignment()))) .stream().filter(a -> { - var edition = mCache.getOrThrow(a.getModule().getId()).getEdition(); + var edition = mCache.getRequired(a.getModule().getId()).getEdition(); return isStaffInAll || roleService.rolesForPersonInEdition(edition, person).stream() .noneMatch(roleService::isStaff); }).toList(); Map<Long, String> assignments = allowedAssignments.stream() .collect(Collectors.toMap(AssignmentDetailsDTO::getId, a -> { - var edition = mCache.getOrThrow(a.getModule().getId()).getEdition(); + var edition = mCache.getRequired(a.getModule().getId()).getEdition(); return session.getEditions().size() == 1 ? a.getName() - : eCache.getOrThrow(edition.getId()).getCourse().getName() + " - " + : eCache.getRequired(edition.getId()).getCourse().getName() + " - " + a.getName(); })); Map<Long, Long> notEnqueueAble = allowedAssignments.stream().filter(a -> { - var edition = mCache.getOrThrow(a.getModule().getId()).getEdition(); + var edition = mCache.getRequired(a.getModule().getId()).getEdition(); return roleService.rolesForPersonInEdition(edition, person).isEmpty(); }).collect(Collectors.toMap(AssignmentDetailsDTO::getId, a -> { - var edition = mCache.getOrThrow(a.getModule().getId()).getEdition(); + var edition = mCache.getRequired(a.getModule().getId()).getEdition(); return edition.getId(); })); @@ -730,9 +729,11 @@ public class LabController { .collect(Collectors.groupingBy(AllowedRequest::getAssignment, Collectors.mapping(ar -> ar.getType().displayName(), Collectors.toSet())))); } else { - model.addAttribute("modules", mCache.get(qSession.getModules().stream()) - .stream().sorted(Comparator.comparing(ModuleDetailsDTO::getName)) - .collect(Collectors.toList())); + model.addAttribute("modules", + mCache.getAndHandle(qSession.getModules().stream(), + moduleID -> qSession.getModules().remove(moduleID)) + .stream().sorted(Comparator.comparing(ModuleDetailsDTO::getName)) + .collect(Collectors.toList())); } } @@ -743,14 +744,14 @@ public class LabController { * @param model The model to fill using edition information. */ private void setSessionEditingPageAttributes(List<Long> editionIds, Model model) { - var editions = eCache.get(editionIds); + var editions = eCache.getAndIgnoreMissing(editionIds); - var modules = mCache.get(editions.stream() + var modules = mCache.getAndIgnoreMissing(editions.stream() .flatMap(e -> e.getModules().stream().map(ModuleSummaryDTO::getId).distinct())); - var assignments = aCache.get(modules.stream() + var assignments = aCache.getAndIgnoreMissing(modules.stream() .flatMap(m -> m.getAssignments().stream().map(AssignmentSummaryDTO::getId)).distinct()); var clusters = cCache - .get(editions.stream().map(e -> e.getCohort().getId()).distinct()) + .getAndIgnoreMissing(editions.stream().map(e -> e.getCohort().getId()).distinct()) .stream().flatMap(cohort -> cohort.getClusters().stream()).distinct() .sorted(Comparator.comparing(ClusterSummaryDTO::getName)).collect(Collectors.toList()); @@ -787,13 +788,13 @@ public class LabController { * @param model The model to fill using lab information. */ private void setSessionEditingPageAttributes(QueueSession<?> qSession, Model model) { - var session = sCache.getOrThrow(qSession.getSession()); + var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession)); model.addAttribute("lSession", session); if (session.getEdition() != null) { - model.addAttribute("edition", eCache.getOrThrow(session.getEdition().getId())); + model.addAttribute("edition", eCache.getRequired(session.getEdition().getId())); } else { - model.addAttribute("ec", ecCache.getOrThrow(session.getEditionCollection().getId())); + model.addAttribute("ec", ecCache.getRequired(session.getEditionCollection().getId())); } setSessionEditingPageAttributes(session.getEditions().stream() @@ -813,7 +814,8 @@ public class LabController { Matcher matcher = testPattern.matcher(request.getRequestURI()); if (matcher.matches() && matcher.groupCount() == 1) { Optional<QueueSession<?>> lab = qsRepository.findById(Long.parseLong(matcher.group(1))); - var session = sCache.getOrThrow(lab.orElseThrow().getSession()); + var session = sCache.getRequired(lab.orElseThrow().getSession(), + id -> ls.deleteSession(lab.get())); var edition = session.getEdition().getId(); return "redirect:/edition/" + edition + "/enrol"; } diff --git a/src/main/java/nl/tudelft/queue/controller/ModuleController.java b/src/main/java/nl/tudelft/queue/controller/ModuleController.java index a86c0aed9e402b5c200de3660f1bbcaf1c33cf5c..2429b42fd28f54bdf50995af7970760a7ba4ccdd 100644 --- a/src/main/java/nl/tudelft/queue/controller/ModuleController.java +++ b/src/main/java/nl/tudelft/queue/controller/ModuleController.java @@ -95,8 +95,8 @@ public class ModuleController { @PreAuthorize("@permissionService.canManageModule(#moduleId)") public String getModuleRemovePage(@PathVariable Long moduleId, Model model) { - var module = mCache.getOrThrow(moduleId); - var edition = eCache.getOrThrow(module.getEdition().getId()); + var module = mCache.getRequired(moduleId); + var edition = eCache.getRequired(module.getEdition().getId()); model.addAttribute("edition", edition); model.addAttribute("_module", module); @@ -113,8 +113,8 @@ public class ModuleController { @PostMapping("/module/{moduleId}/remove") @PreAuthorize("@permissionService.canManageModule(#moduleId)") public String removeModule(@PathVariable Long moduleId) { - var module = mCache.getOrThrow(moduleId); - var edition = eCache.getOrThrow(module.getEdition().getId()); + var module = mCache.getRequired(moduleId); + var edition = eCache.getRequired(module.getEdition().getId()); mApi.deleteModule(moduleId).block(); @@ -131,7 +131,7 @@ public class ModuleController { * @return The Thymeleaf template to resolve. */ private String addCreateModuleAttributes(Long editionId, QueueModuleCreateDTO dto, Model model) { - EditionDetailsDTO edition = eCache.getOrThrow(editionId); + EditionDetailsDTO edition = eCache.getRequired(editionId); model.addAttribute("edition", edition); model.addAttribute("dto", dto); diff --git a/src/main/java/nl/tudelft/queue/controller/RequestController.java b/src/main/java/nl/tudelft/queue/controller/RequestController.java index 5e640964ea03443b68c3e7981ca6e1e42508414f..3d427167a590ce89ef0f733a24399fe2e8e89e2e 100644 --- a/src/main/java/nl/tudelft/queue/controller/RequestController.java +++ b/src/main/java/nl/tudelft/queue/controller/RequestController.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import javax.transaction.Transactional; +import nl.tudelft.labracore.api.AssignmentControllerApi; import nl.tudelft.labracore.api.PersonControllerApi; import nl.tudelft.labracore.api.dto.PersonSummaryDTO; import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson; @@ -74,6 +75,9 @@ public class RequestController { @Autowired private PersonControllerApi pApi; + @Autowired + private AssignmentControllerApi aApi; + @Autowired private RoleDTOService rds; @@ -359,9 +363,10 @@ public class RequestController { @PreAuthorize("@permissionService.canFinishRequest(#request.id)") public String getRequestForwardView(@AuthenticatedPerson Person user, @PathEntity LabRequest request, Model model) { + List<PersonSummaryDTO> assistants = rds - .roles(eCache.getOrThrow( - mCache.getOrThrow(aCache.getOrThrow(request.getAssignment()).getModule().getId()) + .roles(eCache.getRequired( + mCache.getRequired(aCache.getRequired(request.getAssignment()).getModule().getId()) .getEdition().getId()), Set.of(TA, HEAD_TA, TEACHER)) .stream().filter(p -> !p.getId().equals(user.getId())).toList(); @@ -395,7 +400,8 @@ public class RequestController { if (forwardedTo == -1L) { rs.forwardRequestToAnyone(request, assistant, reasonForAssistant); } else { - rs.forwardRequestToPerson(request, assistant, pCache.getOrThrow(forwardedTo), reasonForAssistant); + rs.forwardRequestToPerson(request, assistant, pCache.getRequired(forwardedTo), + reasonForAssistant); } redirectAttributes.addFlashAttribute("message", "Request #" + request.getId() + " forwarded"); diff --git a/src/main/java/nl/tudelft/queue/controller/SharedEditionController.java b/src/main/java/nl/tudelft/queue/controller/SharedEditionController.java index b1fa235f51bff95ac358d54dfe1848cdc3dd7eb4..b2e47e3d8dc15fb88de1a1b54cf7485575bc2032 100644 --- a/src/main/java/nl/tudelft/queue/controller/SharedEditionController.java +++ b/src/main/java/nl/tudelft/queue/controller/SharedEditionController.java @@ -72,8 +72,9 @@ public class SharedEditionController { @GetMapping("/{id}") @PreAuthorize("@permissionService.isAuthenticated()") public String getSharedEdition(@PathVariable Long id, Model model) { - var collection = editionCollectionCacheManager.getOrThrow(id); - var editions = editionCacheManager.get( + var collection = editionCollectionCacheManager.getRequired(id); + + var editions = editionCacheManager.getAndIgnoreMissing( collection.getEditions().stream().map(EditionSummaryDTO::getId).toList()); var teachers = editions.stream().map(edition -> roleDTOService.teachers(edition)) diff --git a/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java b/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java index 480c00ca34b92c714e550127577d66deb9bad5b2..ad84489636fd6a386f27ee11f65699518996cd94 100644 --- a/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java @@ -108,7 +108,7 @@ public abstract class QueueSessionCreateDTO<D extends QueueSession<?>> extends C * @return Room details objects representing the currently selected rooms. */ public List<RoomDetailsDTO> roomDetails() { - return getBean(RoomCacheManager.class).get(new ArrayList<>(rooms)); + return getBean(RoomCacheManager.class).getAndIgnoreMissing(new ArrayList<>(rooms)); } protected void nonNull(String field, Object o) { diff --git a/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java b/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java index 8b535e49adc7ccb51a75fe5bbc6194b54752fb59..e176a56f2bee60fdac0ffaa8661d44cf32ec2d49 100644 --- a/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java @@ -80,7 +80,7 @@ public class LabRequestCreateDTO extends RequestCreateDTO<LabRequest, Lab> { errors.rejectValue("onlineMode", "Room or online mode not selected"); } - var session = getBean(SessionCacheManager.class).getOrThrow(this.getSession().getSession()); + var session = getBean(SessionCacheManager.class).getRequired(this.getSession().getSession()); if (room != null && session.getRooms().stream().noneMatch(r -> Objects.equals(r.getId(), room))) { errors.rejectValue("room", "A room with id " + room + " is not available in the lab."); } @@ -99,7 +99,7 @@ public class LabRequestCreateDTO extends RequestCreateDTO<LabRequest, Lab> { @Override public Long getModule() { - return getBean(AssignmentCacheManager.class).getOrThrow(assignment) + return getBean(AssignmentCacheManager.class).getRequired(assignment) .getModule().getId(); } diff --git a/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java b/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java index c6f18b3bf902a343f78c1dbfc1c4c47534cc2d54..8e5271d2b7da892ec2f9946b105661f0f2b78ab6 100644 --- a/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java @@ -17,10 +17,11 @@ */ package nl.tudelft.queue.dto.patch; +import static nl.tudelft.librador.SpringContext.getBean; + import java.util.Objects; import lombok.*; -import nl.tudelft.librador.SpringContext; import nl.tudelft.librador.dto.patch.Patch; import nl.tudelft.queue.cache.SessionCacheManager; import nl.tudelft.queue.model.LabRequest; @@ -46,8 +47,8 @@ public class RequestPatchDTO extends Patch<LabRequest> { @Override protected void validate() { - var session = SpringContext.getBean(SessionCacheManager.class) - .getOrThrow(data.getSession().getSession()); + var session = getBean(SessionCacheManager.class) + .getRequired(data.getSession().getSession()); if (room != null && session.getRooms().stream().noneMatch(r -> Objects.equals(r.getId(), room))) { errors.rejectValue("room", "Room is not found in the surrounding lab"); } diff --git a/src/main/java/nl/tudelft/queue/dto/view/CalendarEntryViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/CalendarEntryViewDTO.java index 5fa5277ca295ae5bad77bd4ec078e2d0b43c343a..65120971b2ddb314f2cc0579e620a706ba8b2ca7 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/CalendarEntryViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/CalendarEntryViewDTO.java @@ -48,7 +48,7 @@ public class CalendarEntryViewDTO extends View<Lab> { public void postApply() { super.postApply(); - session = SpringContext.getBean(SessionCacheManager.class).getOrThrow(data.getSession()); + session = SpringContext.getBean(SessionCacheManager.class).getRequired(data.getSession()); if (data instanceof AbstractSlottedLab) { AbstractSlottedLab lab = (AbstractSlottedLab) data; diff --git a/src/main/java/nl/tudelft/queue/dto/view/ExamRequestViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/ExamRequestViewDTO.java index 519bf9242b1325bbc981465c8965be749eb2e2e5..11e78a080aeff2ba2ea0e4a096afa684b7ae1f9d 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/ExamRequestViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/ExamRequestViewDTO.java @@ -43,6 +43,6 @@ public class ExamRequestViewDTO extends View<LabRequest> { public void postApply() { super.postApply(); - requesterName = getBean(PersonCacheManager.class).getOrThrow(data.getRequester()).getDisplayName(); + requesterName = getBean(PersonCacheManager.class).getRequired(data.getRequester()).getDisplayName(); } } diff --git a/src/main/java/nl/tudelft/queue/dto/view/ExamTimeSlotViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/ExamTimeSlotViewDTO.java index 0c265b3d65b4d1cc5fa9da0525f7dd204e523720..60c7b414eecead943d5e56d882308ce5d7a86a34 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/ExamTimeSlotViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/ExamTimeSlotViewDTO.java @@ -58,7 +58,8 @@ public class ExamTimeSlotViewDTO extends View<ClosableTimeSlot> { handled = data.countHandledRequests(); // Ensure caching of exam request people and then get exam request views - getBean(PersonCacheManager.class).get(data.getRequests().stream().map(LabRequest::getRequester)); + getBean(PersonCacheManager.class) + .getAndIgnoreMissing(data.getRequests().stream().map(LabRequest::getRequester)); examRequests = View.convert(new ArrayList<>(data.getRequests()), ExamRequestViewDTO.class); } diff --git a/src/main/java/nl/tudelft/queue/dto/view/FeedbackViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/FeedbackViewDTO.java index 820b8cf1f57f3d61a6f3661d5525ab7e5be89a6f..9967547da457d10885c5a04b23731bf4b19b9d2d 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/FeedbackViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/FeedbackViewDTO.java @@ -65,7 +65,7 @@ public class FeedbackViewDTO extends View<Feedback> { } private String editionName(Long sessionId) { - var session = getBean(SessionCacheManager.class).getOrThrow(sessionId); + var session = getBean(SessionCacheManager.class).getRequired(sessionId); return (session.getEdition() == null) ? session.getEditionCollection().getName() : session.getEdition().getName(); @@ -91,7 +91,7 @@ public class FeedbackViewDTO extends View<Feedback> { * @return The name of the requesting entity (either the group or the student). */ private String requesterEntityName(Long studentGroupId) { - var studentGroup = getBean(StudentGroupCacheManager.class).getOrThrow(studentGroupId); + var studentGroup = getBean(StudentGroupCacheManager.class).getRequired(studentGroupId); if (studentGroup.getMemberUsernames().size() == 1) { return studentGroup.getMembers().get(0).getPerson().getDisplayName(); } else { diff --git a/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java b/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java index fa7fa48c3cf78d5cec835f680a4f55aa1602a19b..f7e1c4036c824bb4dd8a8e38b9fd810a4b6ec9ef 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java @@ -68,7 +68,7 @@ public class QueueSessionSummaryDTO extends View<QueueSession<?>> { @Override public void postApply() { - var session = getBean(SessionCacheManager.class).getOrThrow(data.getSession()); + var session = getBean(SessionCacheManager.class).getRequired(data.getSession()); this.name = session.getName(); this.readableName = session.getName() + " : " + DateTimeFormatter.ofPattern("HH:mm - dd " + @@ -92,7 +92,7 @@ public class QueueSessionSummaryDTO extends View<QueueSession<?>> { isShared = session.getEditionCollection() != null; if (isShared) { var editionCollection = getBean(EditionCollectionCacheManager.class) - .getOrThrow(session.getEditionCollection().getId()); + .getRequired(session.getEditionCollection().getId()); associatedEditions = editionCollection.getEditions(); } else { associatedEditions = List.of(Objects.requireNonNull(session.getEdition())); diff --git a/src/main/java/nl/tudelft/queue/dto/view/QueueSessionViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/QueueSessionViewDTO.java index efef4fad47d4c9e6928635b36b6a3d33a5749367..a5eb722faa32353a4066d41de35538b473698aa8 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/QueueSessionViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/QueueSessionViewDTO.java @@ -71,9 +71,9 @@ public abstract class QueueSessionViewDTO<D extends QueueSession<?>> extends Vie @Override public void postApply() { - session = getBean(SessionCacheManager.class).getOrThrow(data.getSession()); + session = getBean(SessionCacheManager.class).getRequired(data.getSession()); modules = new HashSet<>( - getBean(ModuleCacheManager.class).get(data.getModules().stream())); + getBean(ModuleCacheManager.class).getAndIgnoreMissing(data.getModules().stream())); status = getBean(LabService.class).getLabStatus(data); Parser parser = Parser.builder() diff --git a/src/main/java/nl/tudelft/queue/dto/view/RequestEventInfoViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/RequestEventInfoViewDTO.java index 26bf22ef2cf184adfe8fd058d671aff50d644984..1ef788c0598961c9252fef2a450d13ff72ff72f4 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/RequestEventInfoViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/RequestEventInfoViewDTO.java @@ -54,7 +54,7 @@ public class RequestEventInfoViewDTO extends View<RequestEventInfo> { @Override public void postApply() { if (data.getAssignedTo() != null) { - assignedTo = SpringContext.getBean(PersonCacheManager.class).getOrThrow(data.getAssignedTo()); + assignedTo = SpringContext.getBean(PersonCacheManager.class).getRequired(data.getAssignedTo()); } } } diff --git a/src/main/java/nl/tudelft/queue/dto/view/RequestViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/RequestViewDTO.java index 7a9cfa5e03592f35bc49db0c9a4df96ae432d22b..5ee5d87cb09dd514549e125c6593d3c109367c39 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/RequestViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/RequestViewDTO.java @@ -76,16 +76,16 @@ public abstract class RequestViewDTO<R extends Request<?>> extends View<R> .orElse(new RoomDetailsDTO().id(-1L).abbreviation("") .name("To be determined") .building(new BuildingSummaryDTO().name("TBD"))); - requester = getBean(PersonCacheManager.class).getOrThrow(data.getRequester()); + requester = getBean(PersonCacheManager.class).getRequired(data.getRequester()); - session = getBean(SessionCacheManager.class).getOrThrow(qSession.getSession()); - studentGroup = getBean(StudentGroupCacheManager.class).getOrThrow(data.getStudentGroup()); + session = getBean(SessionCacheManager.class).getRequired(qSession.getSession()); + studentGroup = getBean(StudentGroupCacheManager.class).getRequired(data.getStudentGroup()); if (session.getEdition() != null) { - edition = getBean(EditionCacheManager.class).getOrThrow(session.getEdition().getId()); + edition = getBean(EditionCacheManager.class).getRequired(session.getEdition().getId()); } else if (!session.getEditions().isEmpty()) { editionCollection = getBean(EditionCollectionCacheManager.class) - .getOrThrow(session.getEditionCollection().getId()); + .getRequired(session.getEditionCollection().getId()); } eventInfo.postApply(); diff --git a/src/main/java/nl/tudelft/queue/dto/view/events/RequestForwardedToPersonEventViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/events/RequestForwardedToPersonEventViewDTO.java index 09201cbd923ba079af05d997c7ff624e153d306f..04b50ba7b0b8af9482586772684379abe50810f1 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/events/RequestForwardedToPersonEventViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/events/RequestForwardedToPersonEventViewDTO.java @@ -38,7 +38,7 @@ public class RequestForwardedToPersonEventViewDTO extends RequestEventViewDTO<Re @Override public void postApply() { - forwardedTo = getBean(PersonCacheManager.class).getOrThrow(data.getForwardedTo()); + forwardedTo = getBean(PersonCacheManager.class).getRequired(data.getForwardedTo()); } @Override diff --git a/src/main/java/nl/tudelft/queue/dto/view/events/RequestHandledEventViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/events/RequestHandledEventViewDTO.java index 22c6aa8200526e83b6136cccf42c663d3d4bd1c0..197ee4cbb73a4fdcb2ce4f675e5c732c9474e501 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/events/RequestHandledEventViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/events/RequestHandledEventViewDTO.java @@ -40,6 +40,6 @@ public abstract class RequestHandledEventViewDTO<T extends RequestHandledEvent> @Override public void postApply() { - assistant = getBean(PersonCacheManager.class).getOrThrow(data.getAssistant()); + assistant = getBean(PersonCacheManager.class).getRequired(data.getAssistant()); } } diff --git a/src/main/java/nl/tudelft/queue/dto/view/events/RequestPickedEventViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/events/RequestPickedEventViewDTO.java index 67db9917b086ef851a923104ef34e7f4ec14c241..fbaf0411e86f5ab0eef647ebb2ecc09d54ea6a99 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/events/RequestPickedEventViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/events/RequestPickedEventViewDTO.java @@ -37,7 +37,7 @@ public class RequestPickedEventViewDTO extends RequestEventViewDTO<RequestPicked @Override public void postApply() { - takenBy = getBean(PersonCacheManager.class).getOrThrow(data.getAssistant()); + takenBy = getBean(PersonCacheManager.class).getRequired(data.getAssistant()); } @Override diff --git a/src/main/java/nl/tudelft/queue/dto/view/events/RequestTakenEventViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/events/RequestTakenEventViewDTO.java index 2054839fef59804d8cdd412fc1eaeb027b356279..26dbaf96286c39470b213da1b6f50a45ff42381a 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/events/RequestTakenEventViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/events/RequestTakenEventViewDTO.java @@ -37,7 +37,7 @@ public class RequestTakenEventViewDTO extends RequestEventViewDTO<RequestTakenEv @Override public void postApply() { - takenBy = getBean(PersonCacheManager.class).getOrThrow(data.getAssistant()); + takenBy = getBean(PersonCacheManager.class).getRequired(data.getAssistant()); } @Override diff --git a/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java index a861a969294985f9d9277ad73eba64ab17916740..9a4efc940b2194e338022daef0840004252e0324 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java +++ b/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java @@ -72,13 +72,13 @@ public class LabRequestViewDTO extends RequestViewDTO<LabRequest> { setRoom(null); } - assignment = getBean(AssignmentCacheManager.class).getOrThrow(data.getAssignment()); + assignment = getBean(AssignmentCacheManager.class).getRequired(data.getAssignment()); timeSlot = data.getTimeSlot(); if (super.getEdition() == null) { - var editionId = getBean(ModuleCacheManager.class).getOrThrow(assignment.getModule().getId()) + var editionId = getBean(ModuleCacheManager.class).getRequired(assignment.getModule().getId()) .getEdition().getId(); - super.setEdition(getBean(EditionCacheManager.class).getOrThrow(editionId)); + super.setEdition(getBean(EditionCacheManager.class).getRequired(editionId)); } var handledEvent = getEvents().stream() diff --git a/src/main/java/nl/tudelft/queue/model/QueueSession.java b/src/main/java/nl/tudelft/queue/model/QueueSession.java index 104b479e2dff390aba2784baa5ac4b6abb5c887b..16502a08391f1ef6a0b326a19281c39fbed392d0 100644 --- a/src/main/java/nl/tudelft/queue/model/QueueSession.java +++ b/src/main/java/nl/tudelft/queue/model/QueueSession.java @@ -49,6 +49,7 @@ import org.hibernate.validator.constraints.UniqueElements; @Inheritance(strategy = InheritanceType.JOINED) @SQLDelete(sql = "UPDATE queue_session SET deleted_at = NOW() WHERE id = ?") public abstract class QueueSession<R extends Request<?>> { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/nl/tudelft/queue/model/constraints/ClusterConstraint.java b/src/main/java/nl/tudelft/queue/model/constraints/ClusterConstraint.java index 66e9b63acddec2c5d230aa9747bd14be4b6664f0..b1666dc37e663dae03c72385c2c214e08627fc70 100644 --- a/src/main/java/nl/tudelft/queue/model/constraints/ClusterConstraint.java +++ b/src/main/java/nl/tudelft/queue/model/constraints/ClusterConstraint.java @@ -17,6 +17,8 @@ */ package nl.tudelft.queue.model.constraints; +import static nl.tudelft.librador.SpringContext.getBean; + import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; @@ -26,6 +28,7 @@ import java.util.stream.Collectors; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.OneToOne; +import javax.transaction.Transactional; import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -34,7 +37,6 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import nl.tudelft.labracore.api.dto.ClusterDetailsDTO; -import nl.tudelft.librador.SpringContext; import nl.tudelft.queue.cache.ClusterCacheManager; import nl.tudelft.queue.dto.create.constraints.ClusterConstraintCreateDTO; import nl.tudelft.queue.model.LabRequestConstraint; @@ -62,11 +64,12 @@ public class ClusterConstraint extends LabRequestConstraint { private QueueSession<?> session; @Override + @Transactional public boolean canCreateRequest(Long personId) { // Get the clusters in the list of allowed clusters and check whether the person is in one of these. // TODO: Optimize this by creating a method for getting cluster ids per person. - return SpringContext.getBean(ClusterCacheManager.class) - .get(clusters.stream()).stream() + return getBean(ClusterCacheManager.class) + .getAndHandle(clusters.stream(), clusterID -> this.clusters.remove(clusterID)).stream() .anyMatch(cluster -> cluster.getPeople().stream() .anyMatch(person -> Objects.equals(person.getId(), personId))); } @@ -78,8 +81,9 @@ public class ClusterConstraint extends LabRequestConstraint { @Override public String constraintDescription() { - return "in cluster " + SpringContext.getBean(ClusterCacheManager.class).get(new ArrayList<>(clusters)) - .stream().map(ClusterDetailsDTO::getName).collect(Collectors.joining(", ")); + return "in cluster " + + getBean(ClusterCacheManager.class).getAndIgnoreMissing(new ArrayList<>(clusters)) + .stream().map(ClusterDetailsDTO::getName).collect(Collectors.joining(", ")); } @Override diff --git a/src/main/java/nl/tudelft/queue/model/labs/CapacitySession.java b/src/main/java/nl/tudelft/queue/model/labs/CapacitySession.java index a419aeaaa21cc2d4436aae8dd32f418fdd2d1f4e..8821fadec0ddbdad0a08075b93b4beaffee04116 100644 --- a/src/main/java/nl/tudelft/queue/model/labs/CapacitySession.java +++ b/src/main/java/nl/tudelft/queue/model/labs/CapacitySession.java @@ -45,6 +45,7 @@ import nl.tudelft.queue.model.Request; import nl.tudelft.queue.model.SelectionRequest; import nl.tudelft.queue.model.embeddables.CapacitySessionConfig; import nl.tudelft.queue.model.enums.QueueSessionType; +import nl.tudelft.queue.service.LabService; @Data @Entity @@ -65,7 +66,8 @@ public class CapacitySession extends QueueSession<SelectionRequest> { @Override public boolean allowsRequest(Request<?> request) { - var session = getBean(SessionCacheManager.class).getOrThrow(getSession()); + var session = getBean(SessionCacheManager.class).getRequired(getSession(), + id -> getBean(LabService.class).deleteSession(this)); boolean allowed = request instanceof SelectionRequest && super.allowsRequest(request); if (capacitySessionConfig.getProcedure().isFcfs()) { diff --git a/src/main/java/nl/tudelft/queue/model/labs/Lab.java b/src/main/java/nl/tudelft/queue/model/labs/Lab.java index 0ced510627ef11c7247a541816426bf0c1df6f48..da8157a3dfd6519fddadd1c2624cb93a41d74c2f 100644 --- a/src/main/java/nl/tudelft/queue/model/labs/Lab.java +++ b/src/main/java/nl/tudelft/queue/model/labs/Lab.java @@ -43,6 +43,7 @@ import nl.tudelft.queue.model.embeddables.AllowedRequest; import nl.tudelft.queue.model.enums.CommunicationMethod; import nl.tudelft.queue.model.enums.OnlineMode; import nl.tudelft.queue.model.enums.RequestType; +import nl.tudelft.queue.service.LabService; import org.hibernate.validator.constraints.UniqueElements; @@ -134,7 +135,9 @@ public abstract class Lab extends QueueSession<LabRequest> { @Override public boolean allowsRequest(Request<?> request) { - var session = getBean(SessionCacheManager.class).getOrThrow(getSession()); + + var session = getBean(SessionCacheManager.class).getRequired(getSession(), + id -> getBean(LabService.class).deleteSession(this)); return request instanceof LabRequest && super.allowsRequest(request) && allowsRequest((LabRequest) request) diff --git a/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java b/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java index 3ef20d8449cb8ccddd0fe2b5fa8bb20b3e829d8e..db7b74d5a5d3ec86aab1a31e623d6b4d7d60404e 100644 --- a/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java +++ b/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java @@ -82,7 +82,8 @@ public class RequestCreatedMessage extends View<LabRequest> implements Message { courseId = view.getEdition().getCourse().getId(); } else if (view.getEditionCollection() != null) { ecId = view.getEditionCollection().getId(); - editionId = getBean(ModuleCacheManager.class).getOrThrow(view.getAssignment().getModule().getId()) + editionId = getBean(ModuleCacheManager.class) + .getRequired(view.getAssignment().getModule().getId()) .getEdition().getId(); } organizationName = view.organizationName(); diff --git a/src/main/java/nl/tudelft/queue/realtime/messages/RequestTakenMessage.java b/src/main/java/nl/tudelft/queue/realtime/messages/RequestTakenMessage.java index b269bb099835c8b16b12e04fd9b137f1048b1b94..887ce717a8eea03d08021ff8a29a66fb0f8a53d0 100644 --- a/src/main/java/nl/tudelft/queue/realtime/messages/RequestTakenMessage.java +++ b/src/main/java/nl/tudelft/queue/realtime/messages/RequestTakenMessage.java @@ -38,7 +38,7 @@ public class RequestTakenMessage extends RequestStatusUpdateMessage<RequestTaken public void postApply() { super.postApply(); - takenBy = getBean(PersonCacheManager.class).getOrThrow(data.getAssistant()).getDisplayName(); + takenBy = getBean(PersonCacheManager.class).getRequired(data.getAssistant()).getDisplayName(); } @Override diff --git a/src/main/java/nl/tudelft/queue/service/CapacitySessionService.java b/src/main/java/nl/tudelft/queue/service/CapacitySessionService.java index b7817b290e1e5f5d05ff36d8c93bcb3561138b0c..bfd9b51974731a2ff8d813bad042cd701bf25990 100644 --- a/src/main/java/nl/tudelft/queue/service/CapacitySessionService.java +++ b/src/main/java/nl/tudelft/queue/service/CapacitySessionService.java @@ -41,6 +41,7 @@ import nl.tudelft.queue.repository.CapacitySessionRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -63,6 +64,10 @@ public class CapacitySessionService { @Autowired private PersonCacheManager pCache; + @Autowired + @Lazy + private LabService ls; + private SessionCacheManager sCache; private StudentGroupCacheManager sgCache; @@ -74,7 +79,7 @@ public class CapacitySessionService { * @return The number of students in the group. */ private int countStudentMembers(Long sgId) { - return (int) sgCache.getOrThrow(sgId).getMembers().stream() + return (int) sgCache.getRequired(sgId).getMembers().stream() .filter(role -> role.getType() == RolePersonLayer1DTO.TypeEnum.STUDENT) .count(); } @@ -245,7 +250,7 @@ public class CapacitySessionService { * @return The number of people that should be selected. */ private int getAmountToSelect(CapacitySession lab, List<SelectionRequest> alreadySelectedRequests) { - var session = sCache.getOrThrow(lab.getSession()); + var session = sCache.getRequired(lab.getSession(), id -> ls.deleteSession(lab)); // Count the number of requests that have already been selected in case someone gets manually selected. int countAlreadySelected = (int) alreadySelectedRequests.stream() @@ -263,21 +268,6 @@ public class CapacitySessionService { return amountToSelect; } - /** - * Initializes all caches that are used when running a selection. Pre-fetches all data required for a lab - * so that no more fetched need to be done during the algorithm. - * - * @param labs The labs that selections will be run over. - */ - private void initializeCaches(List<CapacitySession> labs) { - sCache = new SessionCacheManager(sApi); - sgCache = new StudentGroupCacheManager(sgApi, pCache); - - sCache.get(labs.stream().map(CapacitySession::getSession)); - sgCache.get(labs.stream().flatMap(lab -> lab.getRequests().stream()) - .map(SelectionRequest::getStudentGroup)); - } - /** * Divides the given list of requests that were selected to come over the rooms that are available for the * lab automatically. Student groups are assigned to a room in the order in which they were selected until @@ -288,7 +278,7 @@ public class CapacitySessionService { */ @Transactional public void divideSelectedRequests(CapacitySession qSession, List<SelectionRequest> selectedRequests) { - var session = sCache.getOrThrow(qSession.getSession()); + var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession)); // For every room in the session, initialize a count Map<RoomDetailsDTO, Integer> occupancy = new HashMap<>(); @@ -329,6 +319,22 @@ public class CapacitySessionService { } } + /** + * Initializes all caches that are used when running a selection. Pre-fetches all data required for a lab + * so that no more fetched need to be done during the algorithm. + * + * @param labs The labs that selections will be run over. + */ + @Transactional + public void initializeCaches(List<CapacitySession> labs) { + sCache = new SessionCacheManager(sApi); + sgCache = new StudentGroupCacheManager(sgApi, pCache); + + sCache.getAndHandleAll(labs.stream().map(CapacitySession::getSession), ls.deleteSessionsByIds()); + sgCache.getAndIgnoreMissing(labs.stream().flatMap(lab -> lab.getRequests().stream()) + .map(SelectionRequest::getStudentGroup)); + } + /** * Runs every minute on the 3rd second of the minute. This method finds all capacity-labs that have to * have their requests picked. For each of these labs, it selects students up to the capacity of the rooms @@ -380,7 +386,8 @@ public class CapacitySessionService { } // If we have a fcfs lab we should select new students immediatly. if (session.getCapacitySessionConfig().getProcedure().isFcfs()) { - for (RoomDetailsDTO room : sCache.getOrThrow(session.getSession()).getRooms()) { + for (RoomDetailsDTO room : sCache + .getRequired(session.getSession(), id -> ls.deleteSession(session)).getRooms()) { session.getRequests().stream() .filter(selectionRequest -> !selectionRequest.getEventInfo().getStatus().isSelected()) .peek((r) -> r.setRoom(room.getId())) diff --git a/src/main/java/nl/tudelft/queue/service/EditionStatusService.java b/src/main/java/nl/tudelft/queue/service/EditionStatusService.java index b2163bb56226d3903ea748a0ea772b49d82b88e6..896d4c6cd084f528483b26d7188b64a2be10f4f1 100644 --- a/src/main/java/nl/tudelft/queue/service/EditionStatusService.java +++ b/src/main/java/nl/tudelft/queue/service/EditionStatusService.java @@ -26,6 +26,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import javax.transaction.Transactional; + import nl.tudelft.labracore.api.dto.AssignmentDetailsDTO; import nl.tudelft.labracore.api.dto.PersonSummaryDTO; import nl.tudelft.labracore.api.dto.RoomDetailsDTO; @@ -40,6 +42,7 @@ import nl.tudelft.queue.model.labs.Lab; import nl.tudelft.queue.repository.LabRequestRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @Service @@ -59,6 +62,10 @@ public class EditionStatusService { @Autowired private PersonCacheManager pCache; + @Autowired + @Lazy + private LabService ls; + /** * Creates the buckets used for counting frequencies of events occurring. Buckets are based on the minimum * time of day a lab in the given list of labs starts and the maximum time of day such a lab ends. The @@ -74,9 +81,10 @@ public class EditionStatusService { * @param nBuckets The number of buckets to create. * @return A treeset of interval start times. */ + @Transactional public TreeSet<Long> createBucketsOverCourse(List<Lab> labs, int nBuckets) { - var sessions = sCache.get(labs.stream().map(Lab::getSession)); + var sessions = sCache.getAndHandleAll(labs.stream().map(Lab::getSession), ls.deleteSessionsByIds()); long min = sessions.stream() .map(l -> l.getStart().getLong(SECOND_OF_DAY)) @@ -214,7 +222,7 @@ public class EditionStatusService { */ public Map<String, Long> countRequestsPerAssignment(List<Long> assignments, List<LabRequest> requests) { - return aCache.get(assignments).stream() + return aCache.getAndIgnoreMissing(assignments).stream() .collect(Collectors.toMap( AssignmentDetailsDTO::getName, a -> countWhere(requests, r -> r.getAssignment().equals(a.getId())))); @@ -230,7 +238,7 @@ public class EditionStatusService { * occurs in the list of requests. */ public Map<String, Long> countRequestsPerRoom(List<Long> rooms, List<LabRequest> requests) { - return rCache.get(rooms).stream() + return rCache.getAndIgnoreMissing(rooms).stream() .distinct() .collect(Collectors.toMap( RoomDetailsDTO::getName, @@ -289,7 +297,7 @@ public class EditionStatusService { .distinct() .filter(Objects::nonNull) .collect(Collectors.toList()); - Map<Long, String> names = pCache.get(ids) + Map<Long, String> names = pCache.getAndIgnoreMissing(ids) .stream() .collect(Collectors.toMap( PersonSummaryDTO::getId, diff --git a/src/main/java/nl/tudelft/queue/service/FeedbackService.java b/src/main/java/nl/tudelft/queue/service/FeedbackService.java index dab0b51811f6e6f36c2b9f3f1d0555acd5dbd581..8dc69ea92dac183abdd159866fd29b64e447c7dd 100644 --- a/src/main/java/nl/tudelft/queue/service/FeedbackService.java +++ b/src/main/java/nl/tudelft/queue/service/FeedbackService.java @@ -58,7 +58,7 @@ public class FeedbackService { * given request. */ public List<PersonSummaryDTO> assistantsInvolvedInRequest(Long requestId) { - return pCache.get(rr.findById(requestId).stream() + return pCache.getAndIgnoreMissing(rr.findById(requestId).stream() .flatMap(request -> request.getEventInfo().involvedAssistants().stream())); } diff --git a/src/main/java/nl/tudelft/queue/service/JitsiService.java b/src/main/java/nl/tudelft/queue/service/JitsiService.java index 9bd2d5990803272eb10f718561d8ec97c369a6b4..204c3576dbc44c0bfb68e7bc8796c1766a30ffc9 100644 --- a/src/main/java/nl/tudelft/queue/service/JitsiService.java +++ b/src/main/java/nl/tudelft/queue/service/JitsiService.java @@ -49,7 +49,7 @@ public class JitsiService { UUID uuid = UUID.randomUUID(); // Lookup the username of the requester and add it to the UUID to make a room name - return pCache.getOrThrow(request.getRequester()).getUsername() + "-" + + return pCache.getRequired(request.getRequester()).getUsername() + "-" + uuid.toString().substring(0, 8); } diff --git a/src/main/java/nl/tudelft/queue/service/LabService.java b/src/main/java/nl/tudelft/queue/service/LabService.java index b10bfd080e7bb3ad7019bd6cbde2893309d2a856..79eea8c1d5f22d102d726949917ec18d5c81f22f 100644 --- a/src/main/java/nl/tudelft/queue/service/LabService.java +++ b/src/main/java/nl/tudelft/queue/service/LabService.java @@ -23,6 +23,7 @@ import static nl.tudelft.queue.misc.QueueSessionStatus.*; import java.io.IOException; import java.time.LocalDateTime; import java.util.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import javax.transaction.Transactional; @@ -132,7 +133,7 @@ public class LabService { * @param model The model to set for Thymeleaf resolution. */ public void setOrganizationInModel(QueueSession<?> lab, Model model) { - var session = sCache.getOrThrow(lab.getSession()); + var session = sCache.getRequired(lab.getSession(), id -> deleteSession(lab)); setOrganizationInModel(session, model); } @@ -145,10 +146,10 @@ public class LabService { */ public void setOrganizationInModel(SessionDetailsDTO session, Model model) { model.addAttribute("edition", - (session.getEdition() != null) ? eCache.getOrThrow(session.getEdition().getId()) : null); + (session.getEdition() != null) ? eCache.getRequired(session.getEdition().getId()) : null); model.addAttribute("ec", (session.getEditionCollection() != null) - ? ecCache.getOrThrow(session.getEditionCollection().getId()) + ? ecCache.getRequired(session.getEditionCollection().getId()) : null); } @@ -167,7 +168,7 @@ public class LabService { header = LabRequestViewDTO.csvHeader(); } String[] finalHeader = header; - String name = sCache.getOrThrow(qSession.getId()).getName(); + String name = sCache.getRequired(qSession.getId(), id -> deleteSession(qSession)).getName(); var requests = rts.convertRequestsToView(qSession.getRequests()); return fs.writeTempResource("session-" + name + "-" + requests.hashCode() + ".csv", () -> CsvHelper.writeCsvValues(finalHeader, requests, ',')); @@ -231,7 +232,7 @@ public class LabService { return GENERIC_CLOSED; } - var session = sCache.getOrThrow(qSession.getSession()); + var session = sCache.getRequired(qSession.getSession(), id -> deleteSession(qSession)); if (session.getEnd().isBefore(now())) { return FINISHED_SESSION; @@ -360,7 +361,20 @@ public class LabService { */ @Transactional public void deleteSession(QueueSession<?> session) { - session.setDeletedAt(now()); + qsr.delete(session); + } + + /** + * Callback for deleting a list of labs/sessions by setting the deletedAt to the current date. + * <p> + * <b>NOTE:</b> This method is not transactional, as it merely returns a lambda, so it should be used in a + * transactional context. + * </p> + * + * @return A consumer which deletes sessions based on the list of ids provided + */ + public Consumer<List<Long>> deleteSessionsByIds() { + return ids -> qsr.deleteAllById(ids); } /** @@ -429,10 +443,11 @@ public class LabService { * @return A list of labs which are all occurring within the specified period. */ public List<Lab> getAllLabsWithinPeriod(Period period) { - var editions = eCache.get(Objects.requireNonNull(eApi.getAllEditionsActiveDuringPeriod(period) - .map(EditionSummaryDTO::getId).collectList().block())); + var editions = eCache + .getAndIgnoreMissing(Objects.requireNonNull(eApi.getAllEditionsActiveDuringPeriod(period) + .map(EditionSummaryDTO::getId).collectList().block())); var sessions = editions.stream().map(EditionDetailsDTO::getSessions).flatMap(List::stream) - .collect(Collectors.toList()); + .toList(); return lr.findAllBySessions( sessions.stream().map(SessionSummaryDTO::getId).collect(Collectors.toList())); @@ -444,9 +459,11 @@ public class LabService { * @param labs The labs to count for. * @return The number of labs which are currenlty open. */ + @Transactional public long countOngoingLabs(List<Lab> labs) { LocalDateTime now = LocalDateTime.now(); - List<SessionDetailsDTO> sessions = sCache.get(labs.stream().map(QueueSession::getSession)); + List<SessionDetailsDTO> sessions = sCache.getAndHandleAll(labs.stream().map(QueueSession::getSession), + deleteSessionsByIds()); return sessions.stream().filter(s -> s.getStart().isBefore(now) && s.getEnd().isAfter(now)).count(); } @@ -465,7 +482,8 @@ public class LabService { case EXAM: AbstractSlottedLab<TimeSlot> lab = (AbstractSlottedLab<TimeSlot>) l; return lab.getSlottedLabConfig().getSelectionOpensAt().isBefore(now) - && sCache.getOrThrow(lab.getSession()).getEnd().isAfter(now); + && sCache.getRequired(lab.getSession(), id -> deleteSession(lab)).getEnd() + .isAfter(now); default: return false; } @@ -481,10 +499,10 @@ public class LabService { * @return A list of assignments a TA is allowed to get. */ public List<AssignmentSummaryDTO> getAllowedAssignmentsInLab(Lab lab, Person assistant) { - var sessionDetails = sCache.getOrThrow(lab.getSession()); + var sessionDetails = sCache.getRequired(lab.getSession(), id -> deleteSession(lab)); var allowedAssignments = sessionDetails.getAssignments(); if (sessionDetails.getEditionCollection() != null) { - var collection = ecCache.getOrThrow(sessionDetails.getEditionCollection().getId()); + var collection = ecCache.getRequired(sessionDetails.getEditionCollection().getId()); var roles = collection.getEditions().stream().map(e -> rService.rolesForPersonInEdition(e, assistant)).flatMap(List::stream).filter(rService::isStaff).toList(); allowedAssignments = roles.stream() diff --git a/src/main/java/nl/tudelft/queue/service/PermissionService.java b/src/main/java/nl/tudelft/queue/service/PermissionService.java index 85c2a68a6f1b6537c165a7b959a1481814a0c32e..809842b40fb0853050eda4ca013173adae2db71b 100644 --- a/src/main/java/nl/tudelft/queue/service/PermissionService.java +++ b/src/main/java/nl/tudelft/queue/service/PermissionService.java @@ -281,7 +281,8 @@ public class PermissionService { Function2<Person, RoleDetailsDTO.TypeEnum, Boolean> f) { return withAuthenticatedUser( person -> rCache - .get(editionIds.stream().map(e -> new Id().editionId(e).personId(person.getId()))) + .getAndIgnoreMissing( + editionIds.stream().map(e -> new Id().editionId(e).personId(person.getId()))) .stream().filter(Objects::nonNull) .anyMatch(role -> f.apply(person, role.getType()))); } diff --git a/src/main/java/nl/tudelft/queue/service/RequestService.java b/src/main/java/nl/tudelft/queue/service/RequestService.java index f6c047af11a8f187ddbeef997b5a7cd9daff8bdb..c0f89203132a54de64a2fd032b2fd08a8483b0e6 100644 --- a/src/main/java/nl/tudelft/queue/service/RequestService.java +++ b/src/main/java/nl/tudelft/queue/service/RequestService.java @@ -19,6 +19,7 @@ package nl.tudelft.queue.service; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -57,6 +58,7 @@ import reactor.core.publisher.Mono; @Service public class RequestService { + @Autowired private RequestRepository rr; @@ -109,7 +111,8 @@ public class RequestService { @Autowired private PermissionService permissionService; - private static final ReentrantLock lock = new ReentrantLock(); + private static final ReentrantLock requestTakingLock = new ReentrantLock(); + private static final Set<Long> currentlyEnqueuing = ConcurrentHashMap.newKeySet(); /** * Creates a request in the request database based on the person that posted the request and the @@ -132,39 +135,44 @@ public class RequestService { throw new AccessDeniedException("Request not allowed"); } - if (request instanceof LabRequest) { - var labRequest = (LabRequest) request; - /* - * TODO in the future, delegate to another service layer component, - * and let it generate a link for the respective online mode. - * This is for the far future, when more online modes need to be supported. - */ - - if (labRequest.getTimeSlot() != null && !labRequest.getTimeSlot().canTakeSlot()) { - throw new AccessDeniedException("Time slot is not available"); - } - - if (((LabRequest) request).getOnlineMode() == OnlineMode.JITSI) { - labRequest.setJitsiRoom(js.createJitsiRoomName(labRequest)); - } - if (((LabRequest) request).getSession().getEnableExperimental()) { - ((LabRequest) request).setQuestionId(qApi.addQuestion(new QuestionCreateDTO() - .edition(new EditionIdDTO() - .id(mCache.getOrThrow(dto.getModule()).getEdition().getId())) - .question(((LabRequest) request).getQuestion())).block()); + try { + if (!currentlyEnqueuing.add(personId)) + return; + if (request instanceof LabRequest labRequest) { + /* + * TODO in the future, delegate to another service layer component, + * and let it generate a link for the respective online mode. + * This is for the far future, when more online modes need to be supported. + */ + + if (labRequest.getTimeSlot() != null && !labRequest.getTimeSlot().canTakeSlot()) { + throw new AccessDeniedException("Time slot is not available"); + } + + if (((LabRequest) request).getOnlineMode() == OnlineMode.JITSI) { + labRequest.setJitsiRoom(js.createJitsiRoomName(labRequest)); + } + if (((LabRequest) request).getSession().getEnableExperimental()) { + ((LabRequest) request).setQuestionId(qApi.addQuestion(new QuestionCreateDTO() + .edition(new EditionIdDTO() + .id(mCache.getRequired(dto.getModule()).getEdition().getId())) + .question(((LabRequest) request).getQuestion())).block()); + } } - } - request = rr.save(request); - var event = rer.applyAndSave(new RequestCreatedEvent(request)); + request = rr.save(request); + var event = rer.applyAndSave(new RequestCreatedEvent(request)); - if (request instanceof SelectionRequest) { - ((SelectionRequest) request).getSession().getRequests().add((SelectionRequest) request); - checkFcfsSelection((SelectionRequest) request); - } + if (request instanceof SelectionRequest) { + ((SelectionRequest) request).getSession().getRequests().add((SelectionRequest) request); + checkFcfsSelection((SelectionRequest) request); + } - if (sendEvent) { - wss.sendRequestCreated(event); + if (sendEvent) { + wss.sendRequestCreated(event); + } + } finally { + currentlyEnqueuing.remove(personId); } } @@ -343,7 +351,7 @@ public class RequestService { @Transactional(Transactional.TxType.REQUIRES_NEW) public Optional<LabRequest> takeNextRequestFromTimeSlot(Person assistant, ClosableTimeSlot timeSlot) { - lock.lock(); + requestTakingLock.lock(); try { // If the person is already working on a request, no new event should be created. @@ -362,7 +370,7 @@ public class RequestService { return request; } finally { - lock.unlock(); + requestTakingLock.unlock(); } } @@ -379,11 +387,11 @@ public class RequestService { */ public Optional<LabRequest> takeNextRequest(Person assistant, Lab lab, RequestTableFilterDTO filter) { - lock.lock(); + requestTakingLock.lock(); try { return getNextRequest(assistant, lab, filter); } finally { - lock.unlock(); + requestTakingLock.unlock(); } } @@ -415,7 +423,7 @@ public class RequestService { */ @Transactional(Transactional.TxType.REQUIRES_NEW) public LabRequest pickRequest(Person assistant, LabRequest request) { - lock.lock(); + requestTakingLock.lock(); try { Optional<LabRequest> oldRequests = lrr.findCurrentlyProcessingRequest(assistant, request.getSession()); @@ -427,7 +435,7 @@ public class RequestService { return request; } finally { - lock.unlock(); + requestTakingLock.unlock(); } } @@ -491,7 +499,7 @@ public class RequestService { return; } - var room = rCache.getOrThrow(request.getRoom()); + var room = rCache.getRequired(request.getRoom()); int openSpaces = request.getSession().openSpacesInRoom(room); request.getSession().getPendingRequestsInRoom(room.getId()).stream() @@ -562,7 +570,7 @@ public class RequestService { */ private StudentGroupDetailsDTO createIndividualStudentGroup(Long sessionId, Long personId, Long moduleId) { - var person = pCache.getOrThrow(personId); + var person = pCache.getRequired(personId); var module = mApi.getModuleById(moduleId).block(); var edition = module.getEdition(); diff --git a/src/main/java/nl/tudelft/queue/service/RequestTableService.java b/src/main/java/nl/tudelft/queue/service/RequestTableService.java index 6e311d9f3f4d8398c772729015e6cb2e5d2601ee..393a309a5d04a21537725ace68e8b2ae1b7a0f3b 100644 --- a/src/main/java/nl/tudelft/queue/service/RequestTableService.java +++ b/src/main/java/nl/tudelft/queue/service/RequestTableService.java @@ -26,6 +26,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import javax.servlet.http.HttpSession; +import javax.transaction.Transactional; import nl.tudelft.labracore.api.AssignmentControllerApi; import nl.tudelft.labracore.api.EditionControllerApi; @@ -186,7 +187,7 @@ public class RequestTableService { public List<QueueSession<?>> addFilterAttributes(Model model, List<QueueSession<?>> labs) { // Fetch the editions currently active and assisted by the user. - List<EditionDetailsDTO> editions = eCache.get(eApi.getAllEditionsCurrentlyAssistedBy() + List<EditionDetailsDTO> editions = eCache.getAndIgnoreMissing(eApi.getAllEditionsCurrentlyAssistedBy() .map(EditionSummaryDTO::getId).collectList().blockOptional().orElse(List.of())); // Get all information necessary to display filters. @@ -219,10 +220,12 @@ public class RequestTableService { */ public List<RequestViewDTO<?>> convertRequestsToView(List<? extends Request<?>> requests) { // Pre-load caches with people, sessions, student groups and rooms - pCache.get(requests.stream().map(Request::getRequester).distinct()); - sCache.get(requests.stream().map(r -> r.getSession().getSession()).distinct()); - sgCache.get(requests.stream().map(Request::getStudentGroup).distinct()); - rCache.get(requests.stream().map(Request::getRoom).filter(Objects::nonNull).distinct()); + pCache.getAndIgnoreMissing(requests.stream().map(Request::getRequester).distinct()); + sCache.getAndHandleAll(requests.stream().map(r -> r.getSession().getSession()).distinct(), + lService.deleteSessionsByIds()); + sgCache.getAndIgnoreMissing(requests.stream().map(Request::getStudentGroup).distinct()); + rCache.getAndIgnoreMissing( + requests.stream().map(Request::getRoom).filter(Objects::nonNull).distinct()); // Map each of the request to view DTOs that individually do cache requests return requests.stream().map(Request::toViewDTO).collect(Collectors.toList()); @@ -280,7 +283,7 @@ public class RequestTableService { private List<PersonSummaryDTO> assistants(List<SessionDetailsDTO> sessions) { return sessions.stream() .flatMap(s -> s.getEditions().stream()).distinct() - .flatMap(e -> erCache.getOrThrow(e.getId()).getRoles().stream()) + .flatMap(e -> erCache.getRequired(e.getId()).getRoles().stream()) .filter(role -> Set.of(TA, HEAD_TA, TEACHER, TEACHER_RO).contains(role.getType())) .map(RolePersonDetailsDTO::getPerson) .distinct() @@ -295,7 +298,7 @@ public class RequestTableService { * @return The list of all rooms used within the given sessions. */ private List<RoomDetailsDTO> rooms(List<SessionDetailsDTO> sessions) { - return rCache.get( + return rCache.getAndIgnoreMissing( sessions.stream().flatMap(l -> l.getRooms().stream().map(RoomDetailsDTO::getId)).distinct()); } @@ -334,9 +337,11 @@ public class RequestTableService { * @param labs The labs of which the sessions need to be looked up. * @return The list of all session details gotten from the list of labs. */ + @Transactional private List<SessionDetailsDTO> sessions(List<? extends QueueSession<?>> labs) { - return sCache - .get(labs.stream().map(QueueSession::getSession).distinct().collect(Collectors.toList())); + return sCache.getAndHandleAll( + labs.stream().map(QueueSession::getSession).distinct().collect(Collectors.toList()), + missedIds -> lService.deleteSessionsByIds()); } /** @@ -347,7 +352,7 @@ public class RequestTableService { * @return The list of all assignments within the given editions. */ private List<AssignmentDetailsDTO> assignments(List<SessionDetailsDTO> sessions) { - return aCache.get(sessions.stream() + return aCache.getAndIgnoreMissing(sessions.stream() .flatMap(s -> s.getAssignments().stream()) .map(AssignmentSummaryDTO::getId).distinct()); } @@ -369,7 +374,7 @@ public class RequestTableService { .requireNonNull(aApi.getAssignmentsWithModules(assignmentIds).collectList().block()); List<String> courseCodes = eCache - .get(assignmentModules.stream() + .getAndIgnoreMissing(assignmentModules.stream() .map(assignment -> assignment.getModule().getEdition().getId())) .stream().map(edition -> edition.getCourse().getCode()).toList(); diff --git a/src/main/java/nl/tudelft/queue/service/RoleDTOService.java b/src/main/java/nl/tudelft/queue/service/RoleDTOService.java index 0fb97e106c57afefa27548b9a928b0c5b7de0d48..440df091fab771c0fb58ee997fa3e869466b4381 100644 --- a/src/main/java/nl/tudelft/queue/service/RoleDTOService.java +++ b/src/main/java/nl/tudelft/queue/service/RoleDTOService.java @@ -53,7 +53,7 @@ public class RoleDTOService { } public List<String> roleNames(EditionDetailsDTO eDto, Set<RolePersonDetailsDTO.TypeEnum> types) { - return roleNames(erCache.getOrThrow(eDto.getId()).getRoles(), types); + return roleNames(erCache.getRequired(eDto.getId()).getRoles(), types); } public List<PersonSummaryDTO> roles(List<RolePersonDetailsDTO> roles, @@ -74,7 +74,7 @@ public class RoleDTOService { public List<PersonSummaryDTO> roles(EditionDetailsDTO eDto, Set<RolePersonDetailsDTO.TypeEnum> types) { - return roles(erCache.getOrThrow(eDto.getId()).getRoles(), types); + return roles(erCache.getRequired(eDto.getId()).getRoles(), types); } /** diff --git a/src/main/resources/templates/edition/view/labs.html b/src/main/resources/templates/edition/view/labs.html index 251c224a9f2f36f570d38a06e19fa64bd60e0f2a..c21a3aa0299d788e4b93affb7dd53f47f9e270b8 100644 --- a/src/main/resources/templates/edition/view/labs.html +++ b/src/main/resources/templates/edition/view/labs.html @@ -48,7 +48,7 @@ </div> <div class="page-sub-header"> - <button type="button" class="btn btn-primary float-right" data-toggle="modal" data-target="#create-modal" th:if="${@permissionService.canManageSessions(edition.id)}"> + <button type="button" class="btn btn-primary float-right" data-toggle="modal" data-target="#create-session-modal" th:if="${@permissionService.canManageSessions(edition.id)}"> <span>Create session</span> </button> <h3>Labs</h3> @@ -131,10 +131,32 @@ </a> </th:block> <th:block th:if="${@permissionService.canManageSessions(edition.id)}"> - <a th:href="@{/lab/{id}/remove(id=${lab.id})}" - class="btn btn-sm btn-danger"> + <div + class="btn btn-sm btn-danger" data-toggle="modal" th:data-target="'#delete-session-modal-' + ${lab.id}"> <i class="fa fa-trash-o" aria-hidden="true"></i> - </a> + </div> + <div class="modal" th:id="'delete-session-modal-' + ${lab.id}" tabindex="-1" role="dialog" aria-hidden="true" th:if="${@permissionService.canManageSessions(edition.id)}"> + <div class="modal-dialog modal-dialog-centered"> + <div class="modal-content" > + <div class="modal-header"> + <h3 class="modal-title" th:text="'Delete Session #' + ${lab.id}"></h3> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <div class="alert alert-danger" th:text="'You are about to delete session #' + ${lab.id} + '!'"> + </div> + <p><strong>WARNING: </strong>Deleting labs that already have existing requests is generally not recommended, as it can cause adverse effects. Therefore, please avoid deleting such labs whenever possible.</p> + </div> + <div class="modal-footer"> + <form th:action="@{/lab/{id}/remove(id=${lab.id})}" method="post"> + <button type="submit" class="btn btn-danger" >Delete Session</button> + </form> + </div> + </div> + </div> + </div> </th:block> <th:block th:if="${@permissionService.canManageSessions(edition.id)}"> <a href="#" @@ -149,7 +171,7 @@ </ul> </th:block> - <div class="modal" id="create-modal" tabindex="-1" role="dialog" aria-hidden="true" th:if="${@permissionService.canManageSessions(edition.id)}"> + <div class="modal" id="create-session-modal" tabindex="-1" role="dialog" aria-hidden="true" th:if="${@permissionService.canManageSessions(edition.id)}"> <div class="modal-dialog modal-dialog-centered"> <form class="modal-content" method="get" th:action="@{/edition/{id}/lab/create(id=${edition.id})}"> <div class="modal-header"> diff --git a/src/main/resources/templates/lab/enqueue.html b/src/main/resources/templates/lab/enqueue.html index e60b8c5cd0d95d46652c3e8e84cb075133d66452..47f5575c3339da9f02e5af149abcccd81e8c5f45 100644 --- a/src/main/resources/templates/lab/enqueue.html +++ b/src/main/resources/templates/lab/enqueue.html @@ -48,7 +48,7 @@ </div> <form th:action="@{/lab/{id}/enqueue/{rType}(id=${qSession.id}, rType=${rType})}" th:object="${request}" method="post" - class="form-horizontal"> + class="form-horizontal" id="enqueue-form"> <th:block layout:fragment="enqueue-body"> </th:block> @@ -61,6 +61,13 @@ </div> </div> </form> + <script> + document.addEventListener("DOMContentLoaded", function () { + document.getElementById("enqueue-form").addEventListener("submit", function () { + document.getElementById("enqueue").setAttribute("disabled", ""); + }); + }); + </script> </section> </body> </html> diff --git a/src/main/resources/templates/lab/enqueue/lab.html b/src/main/resources/templates/lab/enqueue/lab.html index 5a6ba4a4fa378fcfc836206ca1e8a07b3410d277..89538030a9682dd18e000ff973704e44a66744e6 100644 --- a/src/main/resources/templates/lab/enqueue/lab.html +++ b/src/main/resources/templates/lab/enqueue/lab.html @@ -168,8 +168,10 @@ <div class="col-sm-8"> <textarea maxlength="250" th:classappend="${#fields.hasErrors('comment')} ? 'is-invalid'" - class="form-control" id="input-comment" th:field="*{comment}" - placeholder="You can use this field to specify a location within the room, e.g. your cubicle number."></textarea> + class="form-control" id="input-comment" th:field="*{comment}" + th:placeholder="'You can use this field to specify a location within the room, e.g. your cubicle number.'"></textarea> + + <input type="hidden" id="lastKnownLocation" th:value="${lastKnownLocation}" /> <div class="invalid-feedback" th:if="${#fields.hasErrors('comment')}" th:errors="*{comment}"> Comment error @@ -181,6 +183,7 @@ <script src="/js/map_loader.js"></script> <script type="text/javascript" th:inline="javascript"> + function checkEnrolled(val) { $(".enrollment-status").css('display', 'none') $("#enqueue").prop('disabled', false) @@ -191,6 +194,14 @@ } } + $(document).ready(() => { + const lastKnownLocation = $("#lastKnownLocation").val(); + const inputCommentField = $("#input-comment"); + if (lastKnownLocation) { + inputCommentField.val(lastKnownLocation); + } + }); + //<![CDATA[ const spDefaults = $.fn.selectpicker.Constructor.DEFAULTS; spDefaults.mobile = true; diff --git a/src/main/resources/templates/lab/remove.html b/src/main/resources/templates/lab/remove.html deleted file mode 100644 index 7223294af27cf7865ec7da8130505883a9310824..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/lab/remove.html +++ /dev/null @@ -1,51 +0,0 @@ -<!-- - - Queue - A Queueing system that can be used to handle labs in higher education - Copyright (C) 2016-2020 Delft University of Technology - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. - ---> -<!DOCTYPE html> -<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" - layout:decorate="~{edition/view}"> - -<!--@thymesVar id="ec" type="nl.tudelft.labracore.api.dto.EditionCollectionDetailsDTO"--> -<!--@thymesVar id="edition" type="nl.tudelft.labracore.api.dto.EditionDetailsDTO"--> - -<!--@thymesVar id="qSession" type="nl.tudelft.queue.model.QueueSession"--> - -<body> -<section layout:fragment="subcontent"> - <div class="page-sub-header"> - <h3>Remove lab</h3> - </div> - - <form action="#" th:action="@{/lab/{id}/remove(id=${qSession.id})}" th:object="${qSession}" - class="form-horizontal" - method="post"> - <input type="hidden" th:field="*{id}"/> - - Are you sure you want to remove <strong th:text="${'session #' + qSession.id}"></strong>? - - <div class="text-center"> - <button class="btn btn-danger">Delete this session</button> - <small>or <a - th:href="@{/{ou}/{id}/labs(id=${ec == null ? edition.id : ec.id}, ou=${ec == null ? 'edition' : 'shared-edition'})}">go - back</a></small> - </div> - </form> -</section> -</body> -</html> diff --git a/src/main/resources/templates/shared-edition/view.html b/src/main/resources/templates/shared-edition/view.html index 6ba75a80df706ad97aaff8a645d3d82eb100ae55..55250ae704f87972ec168f457ee36f9835145be0 100644 --- a/src/main/resources/templates/shared-edition/view.html +++ b/src/main/resources/templates/shared-edition/view.html @@ -68,7 +68,7 @@ <td> <ul> <li th:each="r : ${instance.getValue()}" - th:text="|${r.getType().getValue()} (${@editionCacheManager.getOrThrow(r.id.editionId).course.name})|"> + th:text="|${r.getType().getValue()} (${@editionCacheManager.getRequired(r.id.editionId).course.name})|"> </li> </ul> </td> diff --git a/src/main/resources/templates/shared-edition/view/session-list.html b/src/main/resources/templates/shared-edition/view/session-list.html index ec18c69fcb07d58a6e293e993236877963895f36..e3d139f186313db6a1e941a7d52b19bc9d00822e 100644 --- a/src/main/resources/templates/shared-edition/view/session-list.html +++ b/src/main/resources/templates/shared-edition/view/session-list.html @@ -59,17 +59,38 @@ <td> <span th:text="${#temporals.format(lab.slot.closesAt, 'dd MMMM yyyy HH:mm')}"></span> <div class="btn-group float-right" th:if="${@permissionService.canManageInAnyEdition(lab.associatedEditions)}"> - <th:block> <a th:href="@{/lab/{id}/edit(id=${lab.id})}" class="btn btn-sm btn-secondary"> <i class="fa fa-pencil" aria-hidden="true"></i> </a> </th:block> <th:block> - <a th:href="@{/lab/{id}/remove(id=${lab.id})}" - class="btn btn-sm btn-danger"> + <div + class="btn btn-sm btn-danger" data-toggle="modal" th:data-target="'#delete-session-modal-' + ${lab.id}"> <i class="fa fa-trash-o" aria-hidden="true"></i> - </a> + </div> + <div class="modal" th:id="'delete-session-modal-' + ${lab.id}" tabindex="-1" role="dialog" aria-hidden="true"> + <div class="modal-dialog modal-dialog-centered"> + <div class="modal-content" > + <div class="modal-header"> + <h3 class="modal-title" th:text="'Delete Session #' + ${lab.id}"></h3> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <div class="alert alert-danger" th:text="'You are about to delete session #' + ${lab.id} + '!'"> + </div> + <p><strong>WARNING: </strong>Deleting labs that already have existing requests is generally not recommended, as it can cause adverse effects. Therefore, please avoid deleting such labs whenever possible.</p> + </div> + <div class="modal-footer"> + <form th:action="@{/lab/{id}/remove(id=${lab.id})}" method="post"> + <button type="submit" class="btn btn-danger" >Delete Session</button> + </form> + </div> + </div> + </div> + </div> </th:block> <th:block> <a href="#" diff --git a/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java b/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java index 0bf830f5bbed3c3a9ef921b6abaa445eb0fe6c2f..ed7227545d245348d943675416dd3eebcc7c146b 100644 --- a/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java +++ b/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java @@ -377,6 +377,7 @@ class LabControllerTest { @Test @WithUserDetails("student5") void processingRequestForLabForwardsToRequests() throws Exception { + LabRequest request = rr.save(LabRequest.builder() .requestType(RequestType.QUESTION) .comment("I am a comment") @@ -385,7 +386,7 @@ class LabControllerTest { .room(room1.getId()) .session(regLab1) .requester(student5.getId()) - .studentGroup(1L) + .studentGroup(sg1.getId()) .build()); rer.applyAndSave(new RequestTakenEvent(request, 0L)); @@ -569,14 +570,6 @@ class LabControllerTest { verify(ls).createSessions(isA(ExamLabCreateDTO.class), any(), eq(LabService.SessionType.SHARED)); } - @Test - @WithUserDetails("admin") - void getLabDeleteViewWorks() throws Exception { - mvc.perform(get("/lab/" + regLab1.getId() + "/remove")) - .andExpect(status().isOk()) - .andExpect(view().name("lab/remove")); - } - @Test @WithUserDetails("admin") void deleteLabWorks() throws Exception { diff --git a/src/test/java/nl/tudelft/queue/service/CapacitySessionServiceTest.java b/src/test/java/nl/tudelft/queue/service/CapacitySessionServiceTest.java index 9d31e6434eb89c198bd8d21395525e9bacb11af6..e49cd8cdedd30a76a670113666b7a00c44188760 100644 --- a/src/test/java/nl/tudelft/queue/service/CapacitySessionServiceTest.java +++ b/src/test/java/nl/tudelft/queue/service/CapacitySessionServiceTest.java @@ -140,7 +140,6 @@ public class CapacitySessionServiceTest { sApiMocker.mock(); sgApiMocker.mock(); - // when(rCache.getOrThrow(8932L)).thenReturn(new RoomDetailsDTO().capacity(5)); } private void mockIndividualStudentGroup(Long moduleId, List<Long> requesterIds, Long studentGroupId) { diff --git a/src/test/java/nl/tudelft/queue/service/JitsiServiceTest.java b/src/test/java/nl/tudelft/queue/service/JitsiServiceTest.java index 0a60552b34cd518d3c03c4b26ba23482b94096d8..905bf17cebbeeb6b5f2ea43db56c7828743c6c5e 100644 --- a/src/test/java/nl/tudelft/queue/service/JitsiServiceTest.java +++ b/src/test/java/nl/tudelft/queue/service/JitsiServiceTest.java @@ -56,7 +56,7 @@ public class JitsiServiceTest { @Test void createdJitsiRoomNameShouldStartWithUsername() { - when(pCache.getOrThrow(request.getRequester())) + when(pCache.getRequired(request.getRequester())) .thenReturn(new PersonSummaryDTO().username("jvdcbaskjhbdsaj")); assertThat(js.createJitsiRoomName(request)) diff --git a/src/test/java/nl/tudelft/queue/service/LabServiceTest.java b/src/test/java/nl/tudelft/queue/service/LabServiceTest.java index cace86da5ba351fa30c85f58253113483f9eafc5..5935702e5ea97ce52b2ea4d31b2395240a355088 100644 --- a/src/test/java/nl/tudelft/queue/service/LabServiceTest.java +++ b/src/test/java/nl/tudelft/queue/service/LabServiceTest.java @@ -120,6 +120,9 @@ class LabServiceTest { @Autowired private PersonApiMocker personApiMocker; + @SpyBean + private QueueSessionRepository qsr; + private SessionDetailsDTO session1; private SessionDetailsDTO session2; private SessionDetailsDTO session3; @@ -487,7 +490,7 @@ class LabServiceTest { @Test void deleteLabWorks() { ls.deleteSession(lab1); - assertThat(lab1.getDeletedAt()).isNotNull(); + verify(qsr, times(1)).delete(any()); } @Test