From 8b655bfb4b7cffe8bd07f2cebbb459da23b94602 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Sat, 16 Mar 2024 15:05:27 +0700 Subject: [PATCH] Refactored repositories, deleted shit code --- .../picker/aspect/UserMetricsAspect.java | 2 +- .../picker/controller/NodeController.java | 8 +- .../picker/controller/UserRoomController.java | 11 +- .../ru/dragonestia/picker/model/Node.java | 5 + .../picker/repository/NodeRepository.java | 5 +- .../picker/repository/RoomRepository.java | 11 +- .../picker/repository/UserRepository.java | 10 +- .../repository/impl/ContainerRepository.java | 47 ++++ .../repository/impl/NodeRepositoryImpl.java | 63 +----- .../repository/impl/PickerRepository.java | 58 ----- .../repository/impl/RoomRepositoryImpl.java | 160 +++----------- .../repository/impl/UserRepositoryImpl.java | 209 +++++------------- .../impl/cache/NodeId2PickerModeCache.java | 25 --- .../impl/container/NodeContainer.java | 104 +++++++++ .../impl/container/RoomContainer.java | 105 +++++++++ .../impl/picker/LeastPickedPicker.java | 16 +- .../repository/impl/picker/RoomPicker.java | 4 +- .../repository/impl/picker/RoomWrapper.java | 25 +-- .../impl/picker/RoundRobinPicker.java | 18 +- .../impl/picker/SequentialFillingPicker.java | 18 +- .../repository/impl/type/UserTransaction.java | 13 ++ .../picker/service/RoomService.java | 5 +- .../picker/service/UserService.java | 4 +- .../picker/service/impl/NodeServiceImpl.java | 9 +- .../picker/service/impl/RoomServiceImpl.java | 14 +- .../picker/service/impl/UserServiceImpl.java | 4 +- .../picker/util/NamingValidator.java | 7 +- .../picker/picker/LeastPickedTests.java | 4 +- .../picker/picker/RoundRobinTests.java | 5 +- .../picker/picker/SequentialFillingTests.java | 4 +- 30 files changed, 452 insertions(+), 521 deletions(-) create mode 100644 server/src/main/java/ru/dragonestia/picker/repository/impl/ContainerRepository.java delete mode 100644 server/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java delete mode 100644 server/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java create mode 100644 server/src/main/java/ru/dragonestia/picker/repository/impl/container/NodeContainer.java create mode 100644 server/src/main/java/ru/dragonestia/picker/repository/impl/container/RoomContainer.java create mode 100644 server/src/main/java/ru/dragonestia/picker/repository/impl/type/UserTransaction.java diff --git a/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java b/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java index c4c240e..ee0003d 100644 --- a/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java +++ b/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java @@ -115,7 +115,7 @@ public class UserMetricsAspect { meterRegistry.remove(data.roomsGauge()); } - @AfterReturning(value = "execution(* ru.dragonestia.picker.repository.RoomRepository.pickFree(ru.dragonestia.picker.model.Node, *)) && args(node, ..)", argNames = "node") + @AfterReturning(value = "execution(* ru.dragonestia.picker.repository.RoomRepository.pick(ru.dragonestia.picker.model.Node, *)) && args(node, ..)", argNames = "node") void onPickRoom(Node node) { data.get(node.getIdentifier()).picksPerMinute().increment(); } diff --git a/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java b/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java index 8bdd6b7..d07e5d5 100644 --- a/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java +++ b/server/src/main/java/ru/dragonestia/picker/controller/NodeController.java @@ -8,17 +8,21 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import ru.dragonestia.picker.api.exception.NodeNotFoundException; import ru.dragonestia.picker.api.model.node.PickingMethod; +import ru.dragonestia.picker.api.model.user.UserDefinition; import ru.dragonestia.picker.api.repository.response.NodeDetailsResponse; import ru.dragonestia.picker.api.repository.response.NodeListResponse; import ru.dragonestia.picker.api.repository.response.PickedRoomResponse; import ru.dragonestia.picker.api.repository.type.NodeIdentifier; +import ru.dragonestia.picker.api.repository.type.UserIdentifier; import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.util.DetailsParser; import ru.dragonestia.picker.util.NamingValidator; import java.util.Arrays; +import java.util.stream.Collectors; @Tag(name = "Nodes", description = "Node management") @RestController @@ -82,7 +86,9 @@ public class NodeController { namingValidator.validateNodeId(nodeId); var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); - var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList()); + var users = Arrays.stream(userIds.split(",")) + .map(userId -> new User(UserIdentifier.of(userId))) + .collect(Collectors.toSet()); var response = roomService.pickAvailable(node, users); return ResponseEntity.ok(response); diff --git a/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java b/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java index 6b5e9b0..180e0fe 100644 --- a/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java +++ b/server/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java @@ -10,8 +10,10 @@ import ru.dragonestia.picker.api.exception.NodeNotFoundException; import ru.dragonestia.picker.api.exception.RoomNotFoundException; import ru.dragonestia.picker.api.repository.response.LinkUsersWithRoomResponse; import ru.dragonestia.picker.api.repository.response.RoomUserListResponse; +import ru.dragonestia.picker.api.repository.type.UserIdentifier; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.UserService; @@ -19,6 +21,7 @@ import ru.dragonestia.picker.util.DetailsParser; import ru.dragonestia.picker.util.NamingValidator; import java.util.Arrays; +import java.util.stream.Collectors; @Tag(name = "Users", description = "User management") @RequiredArgsConstructor @@ -54,7 +57,9 @@ public class UserRoomController { @Parameter(description = "Ignore slot limitation") @RequestParam(name = "force") boolean force ) { var room = getNodeAndRoom(nodeId, roomId).room(); - var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList()); + var users = Arrays.stream(userIds.split(",")) + .map(userId -> new User(UserIdentifier.of(userId))) + .collect(Collectors.toSet()); userService.linkUsersWithRoom(room, users, force); var usedSlots = userService.getRoomUsers(room).size(); return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getMaxSlots())); @@ -69,7 +74,9 @@ public class UserRoomController { ) { var room = getNodeAndRoom(nodeId, roomId).room(); - var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList()); + var users = Arrays.stream(userIds.split(",")) + .map(userId -> new User(UserIdentifier.of(userId))) + .collect(Collectors.toSet()); userService.unlinkUsersFromRoom(room, users); return ResponseEntity.ok().build(); } diff --git a/server/src/main/java/ru/dragonestia/picker/model/Node.java b/server/src/main/java/ru/dragonestia/picker/model/Node.java index 36e704c..ab49bdd 100644 --- a/server/src/main/java/ru/dragonestia/picker/model/Node.java +++ b/server/src/main/java/ru/dragonestia/picker/model/Node.java @@ -58,4 +58,9 @@ public class Node implements INode { } return false; } + + @Override + public String toString() { + return "{Node id='%s'}".formatted(identifier); + } } diff --git a/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java index 7aa60da..86b4183 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java @@ -2,7 +2,6 @@ package ru.dragonestia.picker.repository; import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; -import ru.dragonestia.picker.model.Room; import java.util.List; import java.util.Optional; @@ -11,9 +10,9 @@ public interface NodeRepository { void create(Node node) throws NodeAlreadyExistException; - List delete(Node node); + void delete(Node node); - Optional find(String nodeId); + Optional findById(String nodeId); List all(); } diff --git a/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java index 6910fb1..0b17c3a 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java @@ -1,13 +1,14 @@ package ru.dragonestia.picker.repository; +import ru.dragonestia.picker.api.exception.NoRoomsAvailableException; import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; import java.util.Collection; -import java.util.List; import java.util.Optional; +import java.util.Set; public interface RoomRepository { @@ -17,11 +18,7 @@ public interface RoomRepository { Optional find(Node node, String identifier); - List all(Node node); + Collection all(Node node); - Optional pickFree(Node node, Collection users); - - void onCreateNode(Node node); - - List onRemoveNode(Node node); + Room pick(Node node, Set users) throws NoRoomsAvailableException; } diff --git a/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java index b8ccb9e..88d75d1 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/UserRepository.java @@ -10,17 +10,15 @@ import java.util.Map; public interface UserRepository { - int linkWithRoom(Room room, Collection users, boolean force) throws RoomAreFullException; + void linkWithRoom(Room room, Collection users, boolean force) throws RoomAreFullException; void unlinkWithRoom(Room room, Collection users); - List findAllLinkedUserRooms(User user); + Collection findAllLinkedUserRooms(User user); - void onRemoveRoom(Room room); + Collection usersOf(Room room); - List usersOf(Room room); - - List search(String input); + Collection search(String input); int countAllUsers(); diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/ContainerRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/ContainerRepository.java new file mode 100644 index 0000000..5d9e02c --- /dev/null +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/ContainerRepository.java @@ -0,0 +1,47 @@ +package ru.dragonestia.picker.repository.impl; + +import lombok.extern.log4j.Log4j2; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; +import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; +import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.repository.impl.container.NodeContainer; +import ru.dragonestia.picker.repository.impl.type.UserTransaction; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class ContainerRepository { + + private final Map containers = new ConcurrentHashMap<>(); + + private UserTransaction.Listener transactionListener = transaction -> {}; + + public void create(Node node) throws NodeAlreadyExistException { + if (containers.containsKey(node.getIdentifier())) { + throw new NodeAlreadyExistException(node.getIdentifier()); + } + + var container = new NodeContainer(node, transactionListener); + containers.put(node.getIdentifier(), container); + } + + public void remove(@NotNull String nodeId) { + containers.remove(nodeId); + } + + public @NotNull Optional findById(@NotNull String nodeId) { + return Optional.ofNullable(containers.get(nodeId)); + } + + public @NotNull Collection all() { + return containers.values(); + } + + public void setTransactionListener(@NotNull UserTransaction.Listener transactionListener) { + this.transactionListener = transactionListener; + } +} diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java index defb133..8503c01 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java @@ -1,80 +1,39 @@ package ru.dragonestia.picker.repository.impl; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; -import ru.dragonestia.picker.model.Room; -import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.NodeRepository; -import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache; +import ru.dragonestia.picker.repository.impl.container.NodeContainer; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -@Repository +@Component @RequiredArgsConstructor public class NodeRepositoryImpl implements NodeRepository { - private final RoomRepository roomRepository; - private final PickerRepository pickerRepository; - private final NodeId2PickerModeCache nodeId2PickerModeCache; - private final Map nodeMap = new HashMap<>(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final ContainerRepository containerRepository; @Override public void create(Node node) throws NodeAlreadyExistException { - lock.writeLock().lock(); - try { - if (nodeMap.containsKey(node.getIdentifier())) { - throw new NodeAlreadyExistException(node.getIdentifier()); - } - - nodeMap.put(node.getIdentifier(), node); - var picker = pickerRepository.create(node.getIdentifier(), node.getPickingMethod()); - nodeId2PickerModeCache.put(node.getIdentifier(), picker); - - roomRepository.onCreateNode(node); - } finally { - lock.writeLock().unlock(); - } + containerRepository.create(node); } @Override - public List delete(Node node) { - lock.writeLock().lock(); - try { - nodeMap.remove(node.getIdentifier()); - pickerRepository.remove(node.getIdentifier()); - nodeId2PickerModeCache.remove(node.getIdentifier()); - - return roomRepository.onRemoveNode(node); - } finally { - lock.writeLock().unlock(); - } + public void delete(Node node) { + containerRepository.remove(node.getIdentifier()); } @Override - public Optional find(String nodeId) { - lock.readLock().lock(); - try { - return nodeMap.containsKey(nodeId)? Optional.of(nodeMap.get(nodeId)) : Optional.empty(); - } finally { - lock.readLock().unlock(); - } + public Optional findById(String nodeId) { + return containerRepository.findById(nodeId).map(NodeContainer::getNode); } @Override public List all() { - lock.readLock().lock(); - try { - return nodeMap.values().stream().toList(); - } finally { - lock.readLock().unlock(); - } + return containerRepository.all().stream().map(NodeContainer::getNode).toList(); } } diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java deleted file mode 100644 index 536a0c3..0000000 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java +++ /dev/null @@ -1,58 +0,0 @@ -package ru.dragonestia.picker.repository.impl; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import ru.dragonestia.picker.api.model.node.PickingMethod; -import ru.dragonestia.picker.model.Room; -import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.repository.UserRepository; -import ru.dragonestia.picker.repository.impl.picker.*; - -import java.security.InvalidParameterException; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Component -@RequiredArgsConstructor -public class PickerRepository { - - private final UserRepository userRepository; - private final Map pickers = new ConcurrentHashMap<>(); - - public RoomPicker create(String nodeId, PickingMethod mode) { - var picker = of(mode); - pickers.put(nodeId, picker); - return picker; - } - - public void remove(String nodeId) { - pickers.remove(nodeId); - } - - public RoomPicker find(String nodeId) { - return pickers.get(nodeId); - } - - public Room pick(String nodeId, Collection users) { - return pickers.get(nodeId).pick(users); - } - - private RoomPicker of(PickingMethod mode) { - switch (mode) { - case SEQUENTIAL_FILLING -> { - return new SequentialFillingPicker(userRepository); - } - - case ROUND_ROBIN -> { - return new RoundRobinPicker(userRepository); - } - - case LEAST_PICKED -> { - return new LeastPickedPicker(userRepository); - } - - default -> throw new InvalidParameterException("Taken: " + mode); - } - } -} diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java index 8d76f11..fe570a0 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java @@ -1,163 +1,61 @@ package ru.dragonestia.picker.repository.impl; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Component; +import ru.dragonestia.picker.api.exception.NoRoomsAvailableException; import ru.dragonestia.picker.api.exception.NodeNotFoundException; import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; -import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.repository.RoomRepository; -import ru.dragonestia.picker.repository.UserRepository; +import ru.dragonestia.picker.repository.impl.container.NodeContainer; +import ru.dragonestia.picker.repository.impl.container.RoomContainer; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; -@Repository +@Component @RequiredArgsConstructor public class RoomRepositoryImpl implements RoomRepository { - private final UserRepository userRepository; - private final PickerRepository pickerRepository; - private final Map node2roomsMap = new HashMap<>(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(true); + private final ContainerRepository containerRepository; @Override public void create(Room room) throws RoomAlreadyExistException { - var nodeId = room.getNodeIdentifier(); - - lock.writeLock().lock(); - try { - var node = node2roomsMap.keySet().stream() - .filter(n -> room.getNodeIdentifier().equals(n.getIdentifier())) - .findFirst(); - - if (node.isEmpty()) { - throw new IllegalArgumentException("Node '" + nodeId + "' does not exist"); - } - - var rooms = node2roomsMap.get(node.get()); - if (rooms.containsKey(room.getIdentifier())) { - throw new RoomAlreadyExistException(room.getNodeIdentifier(), room.getIdentifier()); - } - rooms.put(room.getIdentifier(), new RoomContainer(room, new AtomicInteger(0))); - pickerRepository.find(room.getNodeIdentifier()).add(room); - } finally { - lock.writeLock().unlock(); - } + containerRepository.findById(room.getNodeIdentifier()) + .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier())) + .addRoom(room); } @Override public void remove(Room room) { - var nodeId = room.getNodeIdentifier(); - var node = node2roomsMap.keySet().stream() - .filter(n -> room.getNodeIdentifier().equals(n.getIdentifier())) - .findFirst(); - - lock.writeLock().lock(); - try { - if (node.isEmpty()) { - throw new NodeNotFoundException("Node '" + nodeId + "' does not exist"); - } - - node2roomsMap.get(node.get()).remove(room.getIdentifier()); - pickerRepository.find(room.getNodeIdentifier()).remove(room); - - userRepository.onRemoveRoom(room); - } finally { - lock.writeLock().unlock(); - } + containerRepository.findById(room.getNodeIdentifier()) + .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier())) + .removeRoom(room); } @Override public Optional find(Node node, String identifier) { - lock.readLock().lock(); - try { - if (!node2roomsMap.containsKey(node)) { - throw new NodeNotFoundException("Node '" + node.getIdentifier() + "' does not exist"); - } - - var result = node2roomsMap.get(node).getOrDefault(identifier, null); - return result == null? Optional.empty() : Optional.of(result.room()); - } finally { - lock.readLock().unlock(); - } + return containerRepository.findById(node.getIdentifier()) + .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier())) + .findRoomById(identifier) + .map(RoomContainer::getRoom); } @Override - public List all(Node node) { - lock.readLock().lock(); - try { - if (!node2roomsMap.containsKey(node)) { - throw new NodeNotFoundException("Node '%s' does not exists".formatted(node.getIdentifier())); - } - - return node2roomsMap.get(node).values().stream().map(RoomContainer::room).toList(); - } finally { - lock.readLock().unlock(); - } + public Collection all(Node node) { + return containerRepository.findById(node.getIdentifier()) + .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier())) + .allRooms() + .stream().map(RoomContainer::getRoom).toList(); } @Override - public Optional pickFree(Node node, Collection users) { - lock.writeLock().lock(); - try { - if (!node2roomsMap.containsKey(node)) { - throw new NodeNotFoundException("Node '" + node.getIdentifier() + "' does not exist"); - } - - Room room = null; - try { - room = pickerRepository.pick(node.getIdentifier(), users); - } catch (RuntimeException ignore) {} // TODO: may be problem. Check it later - - Optional container = room == null? - Optional.empty() : - Optional.of(node2roomsMap.get(node).get(room.getIdentifier())); - - if (container.isPresent()) { - var cont = container.get(); - var addedUsers = userRepository.linkWithRoom(cont.room(), users, false); - cont.used().getAndAdd(addedUsers); - } - - return container.map(RoomContainer::room); - } finally { - lock.writeLock().unlock(); - } + public Room pick(Node node, Set users) throws NoRoomsAvailableException { + return containerRepository.findById(node.getIdentifier()) + .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier())) + .pick(users); } - - @Override - public void onCreateNode(Node node) { - lock.writeLock().lock(); - try { - node2roomsMap.put(node, new Rooms()); - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public List onRemoveNode(Node node) { - lock.writeLock().lock(); - try { - var deleted = node2roomsMap.get(node).values().stream().map(container -> container.room).toList(); - node2roomsMap.remove(node); - - return deleted; - } finally { - lock.writeLock().unlock(); - } - } - - private record RoomContainer(Room room, AtomicInteger used) { - - public boolean isAvailable(int requiredSlots) { - return room.isAvailable(used.get(), requiredSlots); - } - } - - private static class Rooms extends LinkedHashMap {} } diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java index 63ee5ee..6ffdf9b 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java @@ -1,207 +1,104 @@ package ru.dragonestia.picker.repository.impl; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Component; +import ru.dragonestia.picker.api.exception.NodeNotFoundException; import ru.dragonestia.picker.api.exception.RoomAreFullException; +import ru.dragonestia.picker.api.exception.RoomNotFoundException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.repository.UserRepository; -import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache; -import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker; +import ru.dragonestia.picker.repository.impl.container.RoomContainer; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -@Repository +@Component @RequiredArgsConstructor public class UserRepositoryImpl implements UserRepository { - private final NodeId2PickerModeCache nodeId2PickerModeCache; - private final Map> usersMap = new ConcurrentHashMap<>(); - private final Map> roomUsers = new ConcurrentHashMap<>(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(true); + private final ContainerRepository containerRepository; + + private final Map> userRooms = new ConcurrentHashMap<>(); + + @PostConstruct + void init() { + containerRepository.setTransactionListener(transaction -> { + synchronized (userRooms) { + for (var user: transaction.target()) { + var set = userRooms.computeIfAbsent(user, k -> new HashSet<>()); + set.add(transaction.room()); + } + } + }); + } @Override - public int linkWithRoom(Room room, Collection users, boolean force) throws RoomAreFullException { - var toAdd = new HashSet(); + public void linkWithRoom(Room room, Collection users, boolean force) throws RoomAreFullException { + synchronized (userRooms) { + getRoomContainer(room).addUsers(users, force); - lock.writeLock().lock(); - try { - var path = new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()); - var usersSet = roomUsers.getOrDefault(path, new HashSet<>()); - - if (!force && !room.hasUnlimitedSlots()) { - if (room.getMaxSlots() < usersSet.size() + users.size()) { - throw new RoomAreFullException(room.getNodeIdentifier(), room.getIdentifier()); - } + for (var user: users) { + var set = userRooms.computeIfAbsent(user, k -> new HashSet<>()); + set.add(room); } - - users.forEach(user -> { - var set = usersMap.getOrDefault(user, new HashSet<>()); - if (!set.contains(room)) { - toAdd.add(user); - set.add(room); - } - usersMap.put(user, set); - }); - - usersSet.addAll(toAdd); - roomUsers.put(path, usersSet); - - var picker = nodeId2PickerModeCache.get(room.getNodeIdentifier()); - if (picker instanceof LeastPickedPicker leastPickedPicker) { - leastPickedPicker.updateUsersAmount(room, roomUsers.get(path).size()); - } - } finally { - lock.writeLock().unlock(); } - return toAdd.size(); } @Override public void unlinkWithRoom(Room room, Collection users) { - var counter = new AtomicInteger(); - - lock.writeLock().lock(); - try { - usersMap.forEach((user, set) -> { - if (!set.contains(room)) return; + synchronized (userRooms) { + getRoomContainer(room).removeUsers(users); + for (var user: users) { + var set = userRooms.get(user); + if (set == null) continue; set.remove(room); - counter.incrementAndGet(); - - if (set.isEmpty()) { - usersMap.remove(user); - } - }); - - var path = new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()); - var set = roomUsers.getOrDefault(path, new HashSet<>()); - set.removeAll(users); - if (set.isEmpty()) { - roomUsers.remove(path); - } else { - roomUsers.put(path, set); + if (set.isEmpty()) userRooms.remove(user); } - - var picker = nodeId2PickerModeCache.get(room.getNodeIdentifier()); - if (picker instanceof LeastPickedPicker leastPickedPicker) { - leastPickedPicker.updateUsersAmount(room, set.size()); - } - } finally { - lock.writeLock().unlock(); - } - counter.get(); - } - - @Override - public List findAllLinkedUserRooms(User user) { - lock.writeLock().lock(); - try { - return usersMap.getOrDefault(user, new HashSet<>()).stream().toList(); - } finally { - lock.writeLock().unlock(); } } @Override - public void onRemoveRoom(Room room) { - lock.writeLock().lock(); - try { - var users = roomUsers.remove(new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier())); - - if (users == null) return; - - users.forEach(user -> { - var set = usersMap.getOrDefault(user, new HashSet<>()); - set.remove(room); - - if (set.isEmpty()) { - usersMap.remove(user); - } - }); - } finally { - lock.writeLock().unlock(); - } + public Collection findAllLinkedUserRooms(User user) { + return Collections.unmodifiableSet(userRooms.get(user)); } @Override - public List usersOf(Room room) { - lock.readLock().lock(); - try { - return roomUsers.getOrDefault(new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()), new HashSet<>()) - .stream() - .toList(); - } finally { - lock.readLock().unlock(); - } + public Collection usersOf(Room room) { + return getRoomContainer(room).allUsers(); } @Override - public List search(String input) { - lock.readLock().lock(); - try { - return usersMap.keySet().stream() - .filter(user -> user.getIdentifier().startsWith(input)) - .sorted(Comparator.comparing(User::getIdentifier)) - .toList(); - } finally { - lock.readLock().unlock(); - } + public Collection search(String input) { + return userRooms.keySet().stream().filter(user -> user.getIdentifier().startsWith(input)).toList(); } @Override public int countAllUsers() { - lock.readLock().lock(); - try { - return usersMap.size(); - } finally { - lock.readLock().unlock(); - } + return userRooms.size(); } @Override public Map countUsersForNodes() { - var map = new HashMap>(); - - lock.readLock().lock(); - try { - roomUsers.forEach((path, users) -> { - if (map.containsKey(path.node)) { - map.get(path.node).addAll(users); - return; - } - - map.put(path.node, new HashSet<>(users)); - }); - } finally { - lock.readLock().unlock(); - } - var result = new HashMap(); - map.forEach((node, users) -> result.put(node, users.size())); + + containerRepository.all().forEach(nodeContainer -> { + var nodeId = nodeContainer.getNode().getIdentifier(); + + nodeContainer.allRooms().forEach(roomContainer -> { + result.put(nodeId, result.getOrDefault(nodeId, 0) + roomContainer.countUsers()); + }); + }); return result; } - private record NodeRoomPath(String node, String room) { - - @Override - public int hashCode() { - return Objects.hash(node, room); - } - - @Override - public boolean equals(Object o) { - if (o == null) return false; - if (o == this) return true; - if (o instanceof NodeRoomPath other) { - return other.node().equals(node()) && other.room().equals(room()); - } - return false; - } + private RoomContainer getRoomContainer(Room room) { + return containerRepository.findById(room.getNodeIdentifier()) + .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier())) + .findRoomById(room.getIdentifier()) + .orElseThrow(() -> new RoomNotFoundException(room.getNodeIdentifier(), room.getIdentifier())); } } diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java deleted file mode 100644 index 4e1a005..0000000 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.dragonestia.picker.repository.impl.cache; - -import org.springframework.stereotype.Component; -import ru.dragonestia.picker.repository.impl.picker.RoomPicker; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Component -public class NodeId2PickerModeCache { - - private final Map cache = new ConcurrentHashMap<>(); - - public void put(String nodeId, RoomPicker picker) { - cache.put(nodeId, picker); - } - - public void remove(String nodeId) { - cache.remove(nodeId); - } - - public RoomPicker get(String nodeId) { - return cache.get(nodeId); - } -} diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/container/NodeContainer.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/NodeContainer.java new file mode 100644 index 0000000..cf7fa9b --- /dev/null +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/NodeContainer.java @@ -0,0 +1,104 @@ +package ru.dragonestia.picker.repository.impl.container; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; +import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; +import ru.dragonestia.picker.model.User; +import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker; +import ru.dragonestia.picker.repository.impl.picker.RoomPicker; +import ru.dragonestia.picker.repository.impl.picker.RoundRobinPicker; +import ru.dragonestia.picker.repository.impl.picker.SequentialFillingPicker; +import ru.dragonestia.picker.repository.impl.type.UserTransaction; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class NodeContainer { + + @Getter + private final Node node; + private final UserTransaction.Listener transactionListener; + private final RoomPicker picker; + + private final ReadWriteLock roomLock = new ReentrantReadWriteLock(); + private final Map rooms = new ConcurrentHashMap<>(); + + public NodeContainer(@NotNull Node node, @NotNull UserTransaction.Listener transactionListener) { + this.node = node; + this.transactionListener = transactionListener; + this.picker = initPicker(); + } + + private @NotNull RoomPicker initPicker() { + return switch (node.getPickingMethod()) { + case SEQUENTIAL_FILLING -> new SequentialFillingPicker(); + case ROUND_ROBIN -> new RoundRobinPicker(); + case LEAST_PICKED -> new LeastPickedPicker(); + }; + } + + public void addRoom(Room room) throws RoomAlreadyExistException { + roomLock.writeLock().lock(); + try { + if (rooms.containsKey(room.getIdentifier())) { + throw new RoomAlreadyExistException(node.getIdentifier(), room.getIdentifier()); + } + + var container = new RoomContainer(room, this); + rooms.put(room.getIdentifier(), container); + picker.add(container); + } finally { + roomLock.writeLock().unlock(); + } + } + + public void removeRoom(@NotNull Room room) { + roomLock.writeLock().lock(); + try { + picker.remove(rooms.remove(room.getIdentifier())); + } finally { + roomLock.writeLock().unlock(); + } + } + + public void removeRoomsByIds(@NotNull Collection roomIds) { + roomLock.writeLock().lock(); + try { + roomIds.forEach(roomId -> picker.remove(rooms.remove(roomId))); + } finally { + roomLock.writeLock().unlock(); + } + } + + public @NotNull Optional findRoomById(@NotNull String roomId) { + roomLock.readLock().lock(); + try { + return Optional.ofNullable(rooms.get(roomId)); + } finally { + roomLock.readLock().unlock(); + } + } + + public @NotNull Collection allRooms() { + roomLock.readLock().lock(); + try { + return rooms.values(); + } finally { + roomLock.readLock().unlock(); + } + } + + public @NotNull Room pick(@NotNull Set users) { + var room = picker.pick(users); + transactionListener.accept(new UserTransaction(room.getRoom(), users)); + return room.getRoom(); + } + + public @NotNull RoomPicker getPicker() { + return picker; + } +} diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/container/RoomContainer.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/RoomContainer.java new file mode 100644 index 0000000..285f3ef --- /dev/null +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/container/RoomContainer.java @@ -0,0 +1,105 @@ +package ru.dragonestia.picker.repository.impl.container; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.picker.api.exception.RoomAreFullException; +import ru.dragonestia.picker.model.Room; +import ru.dragonestia.picker.model.User; +import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class RoomContainer { + + @Getter + private final Room room; + private final NodeContainer container; + + private final ReadWriteLock usersLock = new ReentrantReadWriteLock(); + private final Set users = new HashSet<>(); + + public RoomContainer(@NotNull Room room, @NotNull NodeContainer container) { + this.room = room; + this.container = container; + } + + public void addUsers(@NotNull Collection toAdd, boolean force) { + usersLock.writeLock().lock(); + try { + if (force || canAdd0(toAdd.size())) { + users.addAll(toAdd); + noticePickersAboutUserNumberUpdate(); + } else { + throw new RoomAreFullException(room.getNodeIdentifier(), room.getIdentifier()); + } + } finally { + usersLock.writeLock().unlock(); + } + } + + public void removeUsers(@NotNull Collection toRemove) { + usersLock.writeLock().lock(); + try { + users.removeAll(toRemove); + noticePickersAboutUserNumberUpdate(); + } finally { + usersLock.writeLock().unlock(); + } + } + + public @NotNull Collection removeAllUsers() { + usersLock.writeLock().lock(); + try { + var set = new HashSet<>(users); + users.clear(); + noticePickersAboutUserNumberUpdate(); + return set; + } finally { + usersLock.writeLock().unlock(); + } + } + + public @NotNull Collection allUsers() { + usersLock.readLock().lock(); + try { + return Collections.unmodifiableSet(users); + } finally { + usersLock.readLock().unlock(); + } + } + + public int countUsers() { + return users.size(); + } + + private boolean canAdd0(int users) { + return room.hasUnlimitedSlots() || users + countUsers() <= room.getMaxSlots(); + } + + public boolean canAdd(int users) { + try { + return canAdd0(users); + } finally { + usersLock.readLock().unlock(); + } + } + + public boolean canBePicked(int users) { + try { + return !room.isLocked() && canAdd0(users); + } finally { + usersLock.readLock().unlock(); + } + } + + private void noticePickersAboutUserNumberUpdate() { + if (container.getPicker() instanceof LeastPickedPicker picker) { + picker.updateUsersAmount(room, countUsers()); + } + } +} diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java index 4a81985..0ad981c 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java @@ -5,34 +5,30 @@ import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap; +import ru.dragonestia.picker.repository.impl.container.RoomContainer; import java.util.Collection; public class LeastPickedPicker implements RoomPicker { - private final UserRepository userRepository; private final DynamicSortedMap map = new DynamicSortedMap<>(); - public LeastPickedPicker(UserRepository userRepository) { - this.userRepository = userRepository; - } - @Override - public void add(Room room) { + public void add(RoomContainer container) { synchronized (map) { - map.put(new RoomWrapper(room, () -> userRepository.usersOf(room).size())); + map.put(new RoomWrapper(container)); } } @Override - public void remove(Room room) { + public void remove(RoomContainer container) { synchronized (map) { - map.removeById(room.getIdentifier()); + map.removeById(container.getRoom().getIdentifier()); } } @Override - public Room pick(Collection users) { + public RoomContainer pick(Collection users) { RoomWrapper wrapper; synchronized (map) { diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java index b3df329..4e2d2a5 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java @@ -1,10 +1,10 @@ package ru.dragonestia.picker.repository.impl.picker; import ru.dragonestia.picker.api.model.node.PickingMethod; -import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; +import ru.dragonestia.picker.repository.impl.container.RoomContainer; -public interface RoomPicker extends Picker { +public interface RoomPicker extends Picker { PickingMethod getPickingMode(); } diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java index 1d0db9f..ba0dcc2 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomWrapper.java @@ -1,46 +1,43 @@ package ru.dragonestia.picker.repository.impl.picker; -import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList; import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap; +import ru.dragonestia.picker.repository.impl.container.RoomContainer; import java.util.function.Consumer; -import java.util.function.Supplier; -public class RoomWrapper implements ItemWrapper, QueuedLinkedList.Item, DynamicSortedMap.Item { +public class RoomWrapper implements ItemWrapper, QueuedLinkedList.Item, DynamicSortedMap.Item { - private final Room room; - private final Supplier userCountSupplier; + private final RoomContainer container; private Consumer setter; - public RoomWrapper(Room room, Supplier userCountSupplier) { - this.room = room; - this.userCountSupplier = userCountSupplier; + public RoomWrapper(RoomContainer container) { + this.container = container; } @Override public String getId() { - return room.getIdentifier(); + return container.getRoom().getIdentifier(); } @Override public int countUnits() { - return userCountSupplier.get(); + return container.countUsers(); } @Override public int maxUnits() { - return room.getMaxSlots(); + return container.getRoom().getMaxSlots(); } @Override - public Room getItem() { - return room; + public RoomContainer getItem() { + return container; } @Override public boolean canAddUnits(int amount) { - return ItemWrapper.super.canAddUnits(amount) && !room.isLocked(); + return container.canBePicked(amount); } @Override diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java index 3902619..48906db 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java @@ -1,40 +1,34 @@ package ru.dragonestia.picker.repository.impl.picker; import ru.dragonestia.picker.api.model.node.PickingMethod; -import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList; +import ru.dragonestia.picker.repository.impl.container.RoomContainer; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; public class RoundRobinPicker implements RoomPicker { - private final UserRepository userRepository; private final AtomicInteger addition = new AtomicInteger(0); private final QueuedLinkedList list = new QueuedLinkedList<>(wrapper -> wrapper.canAddUnits(addition.get())); - public RoundRobinPicker(UserRepository userRepository) { - this.userRepository = userRepository; - } - @Override - public void add(Room room) { + public void add(RoomContainer container) { synchronized (list) { - list.add(new RoomWrapper(room, () -> userRepository.usersOf(room).size())); + list.add(new RoomWrapper(container)); } } @Override - public void remove(Room room) { + public void remove(RoomContainer container) { synchronized (list) { - list.removeById(room.getIdentifier()); + list.removeById(container.getRoom().getIdentifier()); } } @Override - public Room pick(Collection users) { + public RoomContainer pick(Collection users) { int amount = users.size(); RoomWrapper wrapper; diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java index 9cc221c..a91b27a 100644 --- a/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java @@ -1,9 +1,8 @@ package ru.dragonestia.picker.repository.impl.picker; import ru.dragonestia.picker.api.model.node.PickingMethod; -import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.repository.UserRepository; +import ru.dragonestia.picker.repository.impl.container.RoomContainer; import java.util.Collection; import java.util.LinkedHashMap; @@ -11,29 +10,24 @@ import java.util.Map; public class SequentialFillingPicker implements RoomPicker { - private final UserRepository userRepository; private final Map wrappers = new LinkedHashMap<>(); - public SequentialFillingPicker(UserRepository userRepository) { - this.userRepository = userRepository; - } - @Override - public void add(Room room) { + public void add(RoomContainer container) { synchronized (wrappers) { - wrappers.put(room.getIdentifier(), new RoomWrapper(room, () -> userRepository.usersOf(room).size())); + wrappers.put(container.getRoom().getIdentifier(), new RoomWrapper(container)); } } @Override - public void remove(Room room) { + public void remove(RoomContainer container) { synchronized (wrappers) { - wrappers.remove(room.getIdentifier()); + wrappers.remove(container.getRoom().getIdentifier()); } } @Override - public Room pick(Collection users) { + public RoomContainer pick(Collection users) { int amount = users.size(); synchronized (wrappers) { diff --git a/server/src/main/java/ru/dragonestia/picker/repository/impl/type/UserTransaction.java b/server/src/main/java/ru/dragonestia/picker/repository/impl/type/UserTransaction.java new file mode 100644 index 0000000..1ef9473 --- /dev/null +++ b/server/src/main/java/ru/dragonestia/picker/repository/impl/type/UserTransaction.java @@ -0,0 +1,13 @@ +package ru.dragonestia.picker.repository.impl.type; + +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.picker.model.Room; +import ru.dragonestia.picker.model.User; + +import java.util.Collection; +import java.util.function.Consumer; + +public record UserTransaction(@NotNull Room room, Collection target) { + + public interface Listener extends Consumer {} +} diff --git a/server/src/main/java/ru/dragonestia/picker/service/RoomService.java b/server/src/main/java/ru/dragonestia/picker/service/RoomService.java index 23d39b7..780ee8e 100644 --- a/server/src/main/java/ru/dragonestia/picker/service/RoomService.java +++ b/server/src/main/java/ru/dragonestia/picker/service/RoomService.java @@ -9,6 +9,7 @@ import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -21,11 +22,11 @@ public interface RoomService { Optional find(Node node, String roomId); - List all(Node node); + Collection all(Node node); List getAllRoomsWithDetailsResponse(Node node, Set details); - PickedRoomResponse pickAvailable(Node node, List users); + PickedRoomResponse pickAvailable(Node node, Set users); void updateState(Room room); } diff --git a/server/src/main/java/ru/dragonestia/picker/service/UserService.java b/server/src/main/java/ru/dragonestia/picker/service/UserService.java index be7644f..faafa0f 100644 --- a/server/src/main/java/ru/dragonestia/picker/service/UserService.java +++ b/server/src/main/java/ru/dragonestia/picker/service/UserService.java @@ -14,7 +14,7 @@ import java.util.Set; public interface UserService { - List getUserRooms(User user); + Collection getUserRooms(User user); List getUserRoomsWithDetails(User user, Set details); @@ -22,7 +22,7 @@ public interface UserService { void unlinkUsersFromRoom(Room room, Collection users); - List getRoomUsers(Room room); + Collection getRoomUsers(Room room); List getRoomUsersWithDetailsResponse(Room room, Set details); diff --git a/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java b/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java index 3ec93ed..7a4949a 100644 --- a/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java +++ b/server/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java @@ -8,6 +8,7 @@ import ru.dragonestia.picker.api.model.node.NodeDetails; import ru.dragonestia.picker.api.model.node.ResponseNode; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.repository.NodeRepository; +import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.storage.NodeAndRoomStorage; import ru.dragonestia.picker.util.DetailsExtractor; @@ -23,22 +24,24 @@ import java.util.Set; public class NodeServiceImpl implements NodeService { private final NodeRepository nodeRepository; + private final RoomRepository roomRepository; private final DetailsExtractor detailsExtractor; private final NamingValidator namingValidator; private final NodeAndRoomStorage storage; @Override public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException { - namingValidator.validateNodeId(node.getIdentifier()); nodeRepository.create(node); storage.saveNode(node); } @Override public void remove(Node node) { - for (var room: nodeRepository.delete(node)) { + for (var room: roomRepository.all(node)) { storage.removeRoom(room); } + + nodeRepository.delete(node); storage.removeNode(node); } @@ -58,6 +61,6 @@ public class NodeServiceImpl implements NodeService { @Override public Optional find(String nodeId) { - return nodeRepository.find(nodeId); + return nodeRepository.findById(nodeId); } } diff --git a/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java b/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java index eccfe3d..3d4a6b4 100644 --- a/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java +++ b/server/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java @@ -21,10 +21,7 @@ import ru.dragonestia.picker.storage.NodeAndRoomStorage; import ru.dragonestia.picker.util.DetailsExtractor; import ru.dragonestia.picker.util.NamingValidator; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Log4j2 @@ -43,7 +40,7 @@ public class RoomServiceImpl implements RoomService { public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException, NotPersistedNodeException { namingValidator.validateRoomId(room.getNodeIdentifier(), room.getIdentifier()); - var node = nodeRepository.find(room.getNodeIdentifier()).orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier())); + var node = nodeRepository.findById(room.getNodeIdentifier()).orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier())); if (!node.isPersist() && room.isPersist()) { throw new NotPersistedNodeException(node.getIdentifier(), room.getIdentifier()); } @@ -64,7 +61,7 @@ public class RoomServiceImpl implements RoomService { } @Override - public List all(Node node) { + public Collection all(Node node) { return roomRepository.all(node); } @@ -78,9 +75,8 @@ public class RoomServiceImpl implements RoomService { } @Override - public PickedRoomResponse pickAvailable(Node node, List users) { - var room = roomRepository.pickFree(node, users) - .orElseThrow(() -> new RuntimeException("There are no rooms available. Given users count: " + users.size())); + public PickedRoomResponse pickAvailable(Node node, Set users) { + var room = roomRepository.pick(node, users); var roomUsers = userRepository.usersOf(room); return new PickedRoomResponse( diff --git a/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java b/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java index 0858493..d2848fa 100644 --- a/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java +++ b/server/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java @@ -23,7 +23,7 @@ public class UserServiceImpl implements UserService { private final DetailsExtractor detailsExtractor; @Override - public List getUserRooms(User user) { + public Collection getUserRooms(User user) { return userRepository.findAllLinkedUserRooms(user); } @@ -47,7 +47,7 @@ public class UserServiceImpl implements UserService { } @Override - public List getRoomUsers(Room room) { + public Collection getRoomUsers(Room room) { return userRepository.usersOf(room); } diff --git a/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java b/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java index d49c263..52783a2 100644 --- a/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java +++ b/server/src/main/java/ru/dragonestia/picker/util/NamingValidator.java @@ -8,6 +8,7 @@ import ru.dragonestia.picker.api.repository.type.UserIdentifier; import ru.dragonestia.picker.api.util.IdentifierValidator; import ru.dragonestia.picker.model.User; +import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -30,7 +31,7 @@ public class NamingValidator { return IdentifierValidator.forUser(input); } - public List validateUserIds(List input) throws InvalidUsernamesException { + public void validateUserIds(Collection input) throws InvalidUsernamesException { var users = new LinkedList(); var invalid = new LinkedList(); @@ -44,9 +45,7 @@ public class NamingValidator { } if (!invalid.isEmpty()) { - throw new InvalidUsernamesException(input, invalid); + throw new InvalidUsernamesException(input.stream().toList(), invalid); } - - return users; } } diff --git a/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java b/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java index b8df440..5b2dab3 100644 --- a/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java +++ b/server/src/test/java/ru/dragonestia/picker/picker/LeastPickedTests.java @@ -40,7 +40,7 @@ public class LeastPickedTests { @ParameterizedTest @ArgumentsSource(PickingArgumentProvider.class) void testPicking(String expectedRoomId, int usersAmount) { - var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(usersAmount)); + var roomOpt = roomRepository.pick(node, userFiller.createRandomUsers(usersAmount)); Assertions.assertTrue(roomOpt.isPresent()); var room = roomOpt.get(); @@ -73,7 +73,7 @@ public class LeastPickedTests { @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @Test void testNoOneRoomExpected() { // Take 9 users. expected none result - var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(9)); + var roomOpt = roomRepository.pick(node, userFiller.createRandomUsers(9)); Assertions.assertTrue(roomOpt.isEmpty()); } } diff --git a/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java b/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java index ec23f94..6bc5684 100644 --- a/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java +++ b/server/src/test/java/ru/dragonestia/picker/picker/RoundRobinTests.java @@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; -import ru.dragonestia.picker.api.model.node.INode; import ru.dragonestia.picker.config.FillingNodesConfig; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.repository.RoomRepository; @@ -39,7 +38,7 @@ public class RoundRobinTests { @ParameterizedTest @ArgumentsSource(PickingArgumentProvider.class) void testPicking(String expectedRoomId, int usersAmount) { - var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(usersAmount)); + var roomOpt = roomRepository.pick(node, userFiller.createRandomUsers(usersAmount)); Assertions.assertTrue(roomOpt.isPresent()); var room = roomOpt.get(); @@ -69,7 +68,7 @@ public class RoundRobinTests { @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @Test void testNoOneRoomExpected() { // Take 9 users. expected none result - var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(9)); + var roomOpt = roomRepository.pick(node, userFiller.createRandomUsers(9)); Assertions.assertTrue(roomOpt.isEmpty()); } } diff --git a/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java b/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java index 61fb5c8..3cac375 100644 --- a/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java +++ b/server/src/test/java/ru/dragonestia/picker/picker/SequentialFillingTests.java @@ -40,7 +40,7 @@ public class SequentialFillingTests { @ParameterizedTest @ArgumentsSource(PickingArgumentProvider.class) void testPicking(String expectedRoomId, int usersAmount) { - var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(usersAmount)); + var roomOpt = roomRepository.pick(node, userFiller.createRandomUsers(usersAmount)); Assertions.assertTrue(roomOpt.isPresent()); var room = roomOpt.get(); @@ -70,7 +70,7 @@ public class SequentialFillingTests { @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @Test void testNoOneRoomExpected() { // Take 9 users. expected none result - var roomOpt = roomRepository.pickFree(node, userFiller.createRandomUsers(9)); + var roomOpt = roomRepository.pick(node, userFiller.createRandomUsers(9)); Assertions.assertTrue(roomOpt.isEmpty()); } }