Refactored repositories, deleted shit code

This commit is contained in:
Andrey Terentev 2024-03-16 15:05:27 +07:00 committed by Andrey Terentev
parent 087a67f021
commit 8b655bfb4b
30 changed files with 452 additions and 521 deletions

View File

@ -115,7 +115,7 @@ public class UserMetricsAspect {
meterRegistry.remove(data.roomsGauge()); 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) { void onPickRoom(Node node) {
data.get(node.getIdentifier()).picksPerMinute().increment(); data.get(node.getIdentifier()).picksPerMinute().increment();
} }

View File

@ -8,17 +8,21 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import ru.dragonestia.picker.api.exception.NodeNotFoundException; import ru.dragonestia.picker.api.exception.NodeNotFoundException;
import ru.dragonestia.picker.api.model.node.PickingMethod; 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.NodeDetailsResponse;
import ru.dragonestia.picker.api.repository.response.NodeListResponse; import ru.dragonestia.picker.api.repository.response.NodeListResponse;
import ru.dragonestia.picker.api.repository.response.PickedRoomResponse; import ru.dragonestia.picker.api.repository.response.PickedRoomResponse;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier; 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.Node;
import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.NodeService;
import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.util.DetailsParser; import ru.dragonestia.picker.util.DetailsParser;
import ru.dragonestia.picker.util.NamingValidator; import ru.dragonestia.picker.util.NamingValidator;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Collectors;
@Tag(name = "Nodes", description = "Node management") @Tag(name = "Nodes", description = "Node management")
@RestController @RestController
@ -82,7 +86,9 @@ public class NodeController {
namingValidator.validateNodeId(nodeId); namingValidator.validateNodeId(nodeId);
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(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); var response = roomService.pickAvailable(node, users);
return ResponseEntity.ok(response); return ResponseEntity.ok(response);

View File

@ -10,8 +10,10 @@ import ru.dragonestia.picker.api.exception.NodeNotFoundException;
import ru.dragonestia.picker.api.exception.RoomNotFoundException; import ru.dragonestia.picker.api.exception.RoomNotFoundException;
import ru.dragonestia.picker.api.repository.response.LinkUsersWithRoomResponse; import ru.dragonestia.picker.api.repository.response.LinkUsersWithRoomResponse;
import ru.dragonestia.picker.api.repository.response.RoomUserListResponse; 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.Room;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.NodeService;
import ru.dragonestia.picker.service.UserService; import ru.dragonestia.picker.service.UserService;
@ -19,6 +21,7 @@ import ru.dragonestia.picker.util.DetailsParser;
import ru.dragonestia.picker.util.NamingValidator; import ru.dragonestia.picker.util.NamingValidator;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Collectors;
@Tag(name = "Users", description = "User management") @Tag(name = "Users", description = "User management")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -54,7 +57,9 @@ public class UserRoomController {
@Parameter(description = "Ignore slot limitation") @RequestParam(name = "force") boolean force @Parameter(description = "Ignore slot limitation") @RequestParam(name = "force") boolean force
) { ) {
var room = getNodeAndRoom(nodeId, roomId).room(); 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); userService.linkUsersWithRoom(room, users, force);
var usedSlots = userService.getRoomUsers(room).size(); var usedSlots = userService.getRoomUsers(room).size();
return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getMaxSlots())); return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getMaxSlots()));
@ -69,7 +74,9 @@ public class UserRoomController {
) { ) {
var room = getNodeAndRoom(nodeId, roomId).room(); 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); userService.unlinkUsersFromRoom(room, users);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }

View File

@ -58,4 +58,9 @@ public class Node implements INode {
} }
return false; return false;
} }
@Override
public String toString() {
return "{Node id='%s'}".formatted(identifier);
}
} }

View File

@ -2,7 +2,6 @@ package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.Room;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -11,9 +10,9 @@ public interface NodeRepository {
void create(Node node) throws NodeAlreadyExistException; void create(Node node) throws NodeAlreadyExistException;
List<Room> delete(Node node); void delete(Node node);
Optional<Node> find(String nodeId); Optional<Node> findById(String nodeId);
List<Node> all(); List<Node> all();
} }

View File

@ -1,13 +1,14 @@
package ru.dragonestia.picker.repository; package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
public interface RoomRepository { public interface RoomRepository {
@ -17,11 +18,7 @@ public interface RoomRepository {
Optional<Room> find(Node node, String identifier); Optional<Room> find(Node node, String identifier);
List<Room> all(Node node); Collection<Room> all(Node node);
Optional<Room> pickFree(Node node, Collection<User> users); Room pick(Node node, Set<User> users) throws NoRoomsAvailableException;
void onCreateNode(Node node);
List<Room> onRemoveNode(Node node);
} }

View File

@ -10,17 +10,15 @@ import java.util.Map;
public interface UserRepository { public interface UserRepository {
int linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException; void linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException;
void unlinkWithRoom(Room room, Collection<User> users); void unlinkWithRoom(Room room, Collection<User> users);
List<Room> findAllLinkedUserRooms(User user); Collection<Room> findAllLinkedUserRooms(User user);
void onRemoveRoom(Room room); Collection<User> usersOf(Room room);
List<User> usersOf(Room room); Collection<User> search(String input);
List<User> search(String input);
int countAllUsers(); int countAllUsers();

View File

@ -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<String, NodeContainer> 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<NodeContainer> findById(@NotNull String nodeId) {
return Optional.ofNullable(containers.get(nodeId));
}
public @NotNull Collection<NodeContainer> all() {
return containers.values();
}
public void setTransactionListener(@NotNull UserTransaction.Listener transactionListener) {
this.transactionListener = transactionListener;
}
}

View File

@ -1,80 +1,39 @@
package ru.dragonestia.picker.repository.impl; package ru.dragonestia.picker.repository.impl;
import lombok.RequiredArgsConstructor; 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.api.exception.NodeAlreadyExistException;
import ru.dragonestia.picker.model.Node; 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.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.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Repository @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class NodeRepositoryImpl implements NodeRepository { public class NodeRepositoryImpl implements NodeRepository {
private final RoomRepository roomRepository; private final ContainerRepository containerRepository;
private final PickerRepository pickerRepository;
private final NodeId2PickerModeCache nodeId2PickerModeCache;
private final Map<String, Node> nodeMap = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
@Override @Override
public void create(Node node) throws NodeAlreadyExistException { public void create(Node node) throws NodeAlreadyExistException {
lock.writeLock().lock(); containerRepository.create(node);
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();
}
} }
@Override @Override
public List<Room> delete(Node node) { public void delete(Node node) {
lock.writeLock().lock(); containerRepository.remove(node.getIdentifier());
try {
nodeMap.remove(node.getIdentifier());
pickerRepository.remove(node.getIdentifier());
nodeId2PickerModeCache.remove(node.getIdentifier());
return roomRepository.onRemoveNode(node);
} finally {
lock.writeLock().unlock();
}
} }
@Override @Override
public Optional<Node> find(String nodeId) { public Optional<Node> findById(String nodeId) {
lock.readLock().lock(); return containerRepository.findById(nodeId).map(NodeContainer::getNode);
try {
return nodeMap.containsKey(nodeId)? Optional.of(nodeMap.get(nodeId)) : Optional.empty();
} finally {
lock.readLock().unlock();
}
} }
@Override @Override
public List<Node> all() { public List<Node> all() {
lock.readLock().lock(); return containerRepository.all().stream().map(NodeContainer::getNode).toList();
try {
return nodeMap.values().stream().toList();
} finally {
lock.readLock().unlock();
}
} }
} }

View File

@ -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<String, RoomPicker> 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<User> 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);
}
}
}

View File

@ -1,163 +1,61 @@
package ru.dragonestia.picker.repository.impl; package ru.dragonestia.picker.repository.impl;
import lombok.RequiredArgsConstructor; 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.NodeNotFoundException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.RoomRepository; 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.Collection;
import java.util.concurrent.atomic.AtomicInteger; import java.util.Optional;
import java.util.concurrent.locks.ReadWriteLock; import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Repository @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class RoomRepositoryImpl implements RoomRepository { public class RoomRepositoryImpl implements RoomRepository {
private final UserRepository userRepository; private final ContainerRepository containerRepository;
private final PickerRepository pickerRepository;
private final Map<Node, Rooms> node2roomsMap = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
@Override @Override
public void create(Room room) throws RoomAlreadyExistException { public void create(Room room) throws RoomAlreadyExistException {
var nodeId = room.getNodeIdentifier(); containerRepository.findById(room.getNodeIdentifier())
.orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()))
lock.writeLock().lock(); .addRoom(room);
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();
}
} }
@Override @Override
public void remove(Room room) { public void remove(Room room) {
var nodeId = room.getNodeIdentifier(); containerRepository.findById(room.getNodeIdentifier())
var node = node2roomsMap.keySet().stream() .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()))
.filter(n -> room.getNodeIdentifier().equals(n.getIdentifier())) .removeRoom(room);
.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();
}
} }
@Override @Override
public Optional<Room> find(Node node, String identifier) { public Optional<Room> find(Node node, String identifier) {
lock.readLock().lock(); return containerRepository.findById(node.getIdentifier())
try { .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier()))
if (!node2roomsMap.containsKey(node)) { .findRoomById(identifier)
throw new NodeNotFoundException("Node '" + node.getIdentifier() + "' does not exist"); .map(RoomContainer::getRoom);
}
var result = node2roomsMap.get(node).getOrDefault(identifier, null);
return result == null? Optional.empty() : Optional.of(result.room());
} finally {
lock.readLock().unlock();
}
} }
@Override @Override
public List<Room> all(Node node) { public Collection<Room> all(Node node) {
lock.readLock().lock(); return containerRepository.findById(node.getIdentifier())
try { .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier()))
if (!node2roomsMap.containsKey(node)) { .allRooms()
throw new NodeNotFoundException("Node '%s' does not exists".formatted(node.getIdentifier())); .stream().map(RoomContainer::getRoom).toList();
}
return node2roomsMap.get(node).values().stream().map(RoomContainer::room).toList();
} finally {
lock.readLock().unlock();
}
} }
@Override @Override
public Optional<Room> pickFree(Node node, Collection<User> users) { public Room pick(Node node, Set<User> users) throws NoRoomsAvailableException {
lock.writeLock().lock(); return containerRepository.findById(node.getIdentifier())
try { .orElseThrow(() -> new NodeNotFoundException(node.getIdentifier()))
if (!node2roomsMap.containsKey(node)) { .pick(users);
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<RoomContainer> 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();
}
} }
@Override
public void onCreateNode(Node node) {
lock.writeLock().lock();
try {
node2roomsMap.put(node, new Rooms());
} finally {
lock.writeLock().unlock();
}
}
@Override
public List<Room> 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<String, RoomContainer> {}
} }

View File

@ -1,207 +1,104 @@
package ru.dragonestia.picker.repository.impl; package ru.dragonestia.picker.repository.impl;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; 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.RoomAreFullException;
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.UserRepository;
import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache; import ru.dragonestia.picker.repository.impl.container.RoomContainer;
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; 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 @RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository { public class UserRepositoryImpl implements UserRepository {
private final NodeId2PickerModeCache nodeId2PickerModeCache; private final ContainerRepository containerRepository;
private final Map<User, Set<Room>> usersMap = new ConcurrentHashMap<>();
private final Map<NodeRoomPath, Set<User>> roomUsers = new ConcurrentHashMap<>(); private final Map<User, Set<Room>> userRooms = new ConcurrentHashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
@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 @Override
public int linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException { public void linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException {
var toAdd = new HashSet<User>(); synchronized (userRooms) {
getRoomContainer(room).addUsers(users, force);
lock.writeLock().lock(); for (var user: users) {
try { var set = userRooms.computeIfAbsent(user, k -> new HashSet<>());
var path = new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()); set.add(room);
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());
}
} }
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 @Override
public void unlinkWithRoom(Room room, Collection<User> users) { public void unlinkWithRoom(Room room, Collection<User> users) {
var counter = new AtomicInteger(); synchronized (userRooms) {
getRoomContainer(room).removeUsers(users);
lock.writeLock().lock();
try {
usersMap.forEach((user, set) -> {
if (!set.contains(room)) return;
for (var user: users) {
var set = userRooms.get(user);
if (set == null) continue;
set.remove(room); set.remove(room);
counter.incrementAndGet(); if (set.isEmpty()) userRooms.remove(user);
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);
} }
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<Room> findAllLinkedUserRooms(User user) {
lock.writeLock().lock();
try {
return usersMap.getOrDefault(user, new HashSet<>()).stream().toList();
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public void onRemoveRoom(Room room) { public Collection<Room> findAllLinkedUserRooms(User user) {
lock.writeLock().lock(); return Collections.unmodifiableSet(userRooms.get(user));
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();
}
} }
@Override @Override
public List<User> usersOf(Room room) { public Collection<User> usersOf(Room room) {
lock.readLock().lock(); return getRoomContainer(room).allUsers();
try {
return roomUsers.getOrDefault(new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()), new HashSet<>())
.stream()
.toList();
} finally {
lock.readLock().unlock();
}
} }
@Override @Override
public List<User> search(String input) { public Collection<User> search(String input) {
lock.readLock().lock(); return userRooms.keySet().stream().filter(user -> user.getIdentifier().startsWith(input)).toList();
try {
return usersMap.keySet().stream()
.filter(user -> user.getIdentifier().startsWith(input))
.sorted(Comparator.comparing(User::getIdentifier))
.toList();
} finally {
lock.readLock().unlock();
}
} }
@Override @Override
public int countAllUsers() { public int countAllUsers() {
lock.readLock().lock(); return userRooms.size();
try {
return usersMap.size();
} finally {
lock.readLock().unlock();
}
} }
@Override @Override
public Map<String, Integer> countUsersForNodes() { public Map<String, Integer> countUsersForNodes() {
var map = new HashMap<String, Set<User>>();
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<String, Integer>(); var result = new HashMap<String, Integer>();
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; return result;
} }
private record NodeRoomPath(String node, String room) { private RoomContainer getRoomContainer(Room room) {
return containerRepository.findById(room.getNodeIdentifier())
@Override .orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()))
public int hashCode() { .findRoomById(room.getIdentifier())
return Objects.hash(node, room); .orElseThrow(() -> new RoomNotFoundException(room.getNodeIdentifier(), room.getIdentifier()));
}
@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;
}
} }
} }

View File

@ -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<String, RoomPicker> 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);
}
}

View File

@ -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<String, RoomContainer> 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<String> roomIds) {
roomLock.writeLock().lock();
try {
roomIds.forEach(roomId -> picker.remove(rooms.remove(roomId)));
} finally {
roomLock.writeLock().unlock();
}
}
public @NotNull Optional<RoomContainer> findRoomById(@NotNull String roomId) {
roomLock.readLock().lock();
try {
return Optional.ofNullable(rooms.get(roomId));
} finally {
roomLock.readLock().unlock();
}
}
public @NotNull Collection<RoomContainer> allRooms() {
roomLock.readLock().lock();
try {
return rooms.values();
} finally {
roomLock.readLock().unlock();
}
}
public @NotNull Room pick(@NotNull Set<User> users) {
var room = picker.pick(users);
transactionListener.accept(new UserTransaction(room.getRoom(), users));
return room.getRoom();
}
public @NotNull RoomPicker getPicker() {
return picker;
}
}

View File

@ -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<User> users = new HashSet<>();
public RoomContainer(@NotNull Room room, @NotNull NodeContainer container) {
this.room = room;
this.container = container;
}
public void addUsers(@NotNull Collection<User> 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<User> toRemove) {
usersLock.writeLock().lock();
try {
users.removeAll(toRemove);
noticePickersAboutUserNumberUpdate();
} finally {
usersLock.writeLock().unlock();
}
}
public @NotNull Collection<User> removeAllUsers() {
usersLock.writeLock().lock();
try {
var set = new HashSet<>(users);
users.clear();
noticePickersAboutUserNumberUpdate();
return set;
} finally {
usersLock.writeLock().unlock();
}
}
public @NotNull Collection<User> 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());
}
}
}

View File

@ -5,34 +5,30 @@ import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.UserRepository;
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap; import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
import ru.dragonestia.picker.repository.impl.container.RoomContainer;
import java.util.Collection; import java.util.Collection;
public class LeastPickedPicker implements RoomPicker { public class LeastPickedPicker implements RoomPicker {
private final UserRepository userRepository;
private final DynamicSortedMap<RoomWrapper> map = new DynamicSortedMap<>(); private final DynamicSortedMap<RoomWrapper> map = new DynamicSortedMap<>();
public LeastPickedPicker(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override @Override
public void add(Room room) { public void add(RoomContainer container) {
synchronized (map) { synchronized (map) {
map.put(new RoomWrapper(room, () -> userRepository.usersOf(room).size())); map.put(new RoomWrapper(container));
} }
} }
@Override @Override
public void remove(Room room) { public void remove(RoomContainer container) {
synchronized (map) { synchronized (map) {
map.removeById(room.getIdentifier()); map.removeById(container.getRoom().getIdentifier());
} }
} }
@Override @Override
public Room pick(Collection<User> users) { public RoomContainer pick(Collection<User> users) {
RoomWrapper wrapper; RoomWrapper wrapper;
synchronized (map) { synchronized (map) {

View File

@ -1,10 +1,10 @@
package ru.dragonestia.picker.repository.impl.picker; package ru.dragonestia.picker.repository.impl.picker;
import ru.dragonestia.picker.api.model.node.PickingMethod; import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.impl.container.RoomContainer;
public interface RoomPicker extends Picker<Room, User> { public interface RoomPicker extends Picker<RoomContainer, User> {
PickingMethod getPickingMode(); PickingMethod getPickingMode();
} }

View File

@ -1,46 +1,43 @@
package ru.dragonestia.picker.repository.impl.picker; 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.QueuedLinkedList;
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap; 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.Consumer;
import java.util.function.Supplier;
public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item, DynamicSortedMap.Item { public class RoomWrapper implements ItemWrapper<RoomContainer>, QueuedLinkedList.Item, DynamicSortedMap.Item {
private final Room room; private final RoomContainer container;
private final Supplier<Integer> userCountSupplier;
private Consumer<Integer> setter; private Consumer<Integer> setter;
public RoomWrapper(Room room, Supplier<Integer> userCountSupplier) { public RoomWrapper(RoomContainer container) {
this.room = room; this.container = container;
this.userCountSupplier = userCountSupplier;
} }
@Override @Override
public String getId() { public String getId() {
return room.getIdentifier(); return container.getRoom().getIdentifier();
} }
@Override @Override
public int countUnits() { public int countUnits() {
return userCountSupplier.get(); return container.countUsers();
} }
@Override @Override
public int maxUnits() { public int maxUnits() {
return room.getMaxSlots(); return container.getRoom().getMaxSlots();
} }
@Override @Override
public Room getItem() { public RoomContainer getItem() {
return room; return container;
} }
@Override @Override
public boolean canAddUnits(int amount) { public boolean canAddUnits(int amount) {
return ItemWrapper.super.canAddUnits(amount) && !room.isLocked(); return container.canBePicked(amount);
} }
@Override @Override

View File

@ -1,40 +1,34 @@
package ru.dragonestia.picker.repository.impl.picker; package ru.dragonestia.picker.repository.impl.picker;
import ru.dragonestia.picker.api.model.node.PickingMethod; import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User; 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.collection.QueuedLinkedList;
import ru.dragonestia.picker.repository.impl.container.RoomContainer;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class RoundRobinPicker implements RoomPicker { public class RoundRobinPicker implements RoomPicker {
private final UserRepository userRepository;
private final AtomicInteger addition = new AtomicInteger(0); private final AtomicInteger addition = new AtomicInteger(0);
private final QueuedLinkedList<RoomWrapper> list = new QueuedLinkedList<>(wrapper -> wrapper.canAddUnits(addition.get())); private final QueuedLinkedList<RoomWrapper> list = new QueuedLinkedList<>(wrapper -> wrapper.canAddUnits(addition.get()));
public RoundRobinPicker(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override @Override
public void add(Room room) { public void add(RoomContainer container) {
synchronized (list) { synchronized (list) {
list.add(new RoomWrapper(room, () -> userRepository.usersOf(room).size())); list.add(new RoomWrapper(container));
} }
} }
@Override @Override
public void remove(Room room) { public void remove(RoomContainer container) {
synchronized (list) { synchronized (list) {
list.removeById(room.getIdentifier()); list.removeById(container.getRoom().getIdentifier());
} }
} }
@Override @Override
public Room pick(Collection<User> users) { public RoomContainer pick(Collection<User> users) {
int amount = users.size(); int amount = users.size();
RoomWrapper wrapper; RoomWrapper wrapper;

View File

@ -1,9 +1,8 @@
package ru.dragonestia.picker.repository.impl.picker; package ru.dragonestia.picker.repository.impl.picker;
import ru.dragonestia.picker.api.model.node.PickingMethod; import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User; 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.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -11,29 +10,24 @@ import java.util.Map;
public class SequentialFillingPicker implements RoomPicker { public class SequentialFillingPicker implements RoomPicker {
private final UserRepository userRepository;
private final Map<String, RoomWrapper> wrappers = new LinkedHashMap<>(); private final Map<String, RoomWrapper> wrappers = new LinkedHashMap<>();
public SequentialFillingPicker(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override @Override
public void add(Room room) { public void add(RoomContainer container) {
synchronized (wrappers) { synchronized (wrappers) {
wrappers.put(room.getIdentifier(), new RoomWrapper(room, () -> userRepository.usersOf(room).size())); wrappers.put(container.getRoom().getIdentifier(), new RoomWrapper(container));
} }
} }
@Override @Override
public void remove(Room room) { public void remove(RoomContainer container) {
synchronized (wrappers) { synchronized (wrappers) {
wrappers.remove(room.getIdentifier()); wrappers.remove(container.getRoom().getIdentifier());
} }
} }
@Override @Override
public Room pick(Collection<User> users) { public RoomContainer pick(Collection<User> users) {
int amount = users.size(); int amount = users.size();
synchronized (wrappers) { synchronized (wrappers) {

View File

@ -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<User> target) {
public interface Listener extends Consumer<UserTransaction> {}
}

View File

@ -9,6 +9,7 @@ import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -21,11 +22,11 @@ public interface RoomService {
Optional<Room> find(Node node, String roomId); Optional<Room> find(Node node, String roomId);
List<Room> all(Node node); Collection<Room> all(Node node);
List<ShortResponseRoom> getAllRoomsWithDetailsResponse(Node node, Set<RoomDetails> details); List<ShortResponseRoom> getAllRoomsWithDetailsResponse(Node node, Set<RoomDetails> details);
PickedRoomResponse pickAvailable(Node node, List<User> users); PickedRoomResponse pickAvailable(Node node, Set<User> users);
void updateState(Room room); void updateState(Room room);
} }

View File

@ -14,7 +14,7 @@ import java.util.Set;
public interface UserService { public interface UserService {
List<Room> getUserRooms(User user); Collection<Room> getUserRooms(User user);
List<ShortResponseRoom> getUserRoomsWithDetails(User user, Set<RoomDetails> details); List<ShortResponseRoom> getUserRoomsWithDetails(User user, Set<RoomDetails> details);
@ -22,7 +22,7 @@ public interface UserService {
void unlinkUsersFromRoom(Room room, Collection<User> users); void unlinkUsersFromRoom(Room room, Collection<User> users);
List<User> getRoomUsers(Room room); Collection<User> getRoomUsers(Room room);
List<ResponseUser> getRoomUsersWithDetailsResponse(Room room, Set<UserDetails> details); List<ResponseUser> getRoomUsersWithDetailsResponse(Room room, Set<UserDetails> details);

View File

@ -8,6 +8,7 @@ import ru.dragonestia.picker.api.model.node.NodeDetails;
import ru.dragonestia.picker.api.model.node.ResponseNode; import ru.dragonestia.picker.api.model.node.ResponseNode;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.repository.NodeRepository; import ru.dragonestia.picker.repository.NodeRepository;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.NodeService;
import ru.dragonestia.picker.storage.NodeAndRoomStorage; import ru.dragonestia.picker.storage.NodeAndRoomStorage;
import ru.dragonestia.picker.util.DetailsExtractor; import ru.dragonestia.picker.util.DetailsExtractor;
@ -23,22 +24,24 @@ import java.util.Set;
public class NodeServiceImpl implements NodeService { public class NodeServiceImpl implements NodeService {
private final NodeRepository nodeRepository; private final NodeRepository nodeRepository;
private final RoomRepository roomRepository;
private final DetailsExtractor detailsExtractor; private final DetailsExtractor detailsExtractor;
private final NamingValidator namingValidator; private final NamingValidator namingValidator;
private final NodeAndRoomStorage storage; private final NodeAndRoomStorage storage;
@Override @Override
public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException { public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException {
namingValidator.validateNodeId(node.getIdentifier());
nodeRepository.create(node); nodeRepository.create(node);
storage.saveNode(node); storage.saveNode(node);
} }
@Override @Override
public void remove(Node node) { public void remove(Node node) {
for (var room: nodeRepository.delete(node)) { for (var room: roomRepository.all(node)) {
storage.removeRoom(room); storage.removeRoom(room);
} }
nodeRepository.delete(node);
storage.removeNode(node); storage.removeNode(node);
} }
@ -58,6 +61,6 @@ public class NodeServiceImpl implements NodeService {
@Override @Override
public Optional<Node> find(String nodeId) { public Optional<Node> find(String nodeId) {
return nodeRepository.find(nodeId); return nodeRepository.findById(nodeId);
} }
} }

View File

@ -21,10 +21,7 @@ import ru.dragonestia.picker.storage.NodeAndRoomStorage;
import ru.dragonestia.picker.util.DetailsExtractor; import ru.dragonestia.picker.util.DetailsExtractor;
import ru.dragonestia.picker.util.NamingValidator; import ru.dragonestia.picker.util.NamingValidator;
import java.util.LinkedList; import java.util.*;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Log4j2 @Log4j2
@ -43,7 +40,7 @@ public class RoomServiceImpl implements RoomService {
public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException, NotPersistedNodeException { public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException, NotPersistedNodeException {
namingValidator.validateRoomId(room.getNodeIdentifier(), room.getIdentifier()); 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()) { if (!node.isPersist() && room.isPersist()) {
throw new NotPersistedNodeException(node.getIdentifier(), room.getIdentifier()); throw new NotPersistedNodeException(node.getIdentifier(), room.getIdentifier());
} }
@ -64,7 +61,7 @@ public class RoomServiceImpl implements RoomService {
} }
@Override @Override
public List<Room> all(Node node) { public Collection<Room> all(Node node) {
return roomRepository.all(node); return roomRepository.all(node);
} }
@ -78,9 +75,8 @@ public class RoomServiceImpl implements RoomService {
} }
@Override @Override
public PickedRoomResponse pickAvailable(Node node, List<User> users) { public PickedRoomResponse pickAvailable(Node node, Set<User> users) {
var room = roomRepository.pickFree(node, users) var room = roomRepository.pick(node, users);
.orElseThrow(() -> new RuntimeException("There are no rooms available. Given users count: " + users.size()));
var roomUsers = userRepository.usersOf(room); var roomUsers = userRepository.usersOf(room);
return new PickedRoomResponse( return new PickedRoomResponse(

View File

@ -23,7 +23,7 @@ public class UserServiceImpl implements UserService {
private final DetailsExtractor detailsExtractor; private final DetailsExtractor detailsExtractor;
@Override @Override
public List<Room> getUserRooms(User user) { public Collection<Room> getUserRooms(User user) {
return userRepository.findAllLinkedUserRooms(user); return userRepository.findAllLinkedUserRooms(user);
} }
@ -47,7 +47,7 @@ public class UserServiceImpl implements UserService {
} }
@Override @Override
public List<User> getRoomUsers(Room room) { public Collection<User> getRoomUsers(Room room) {
return userRepository.usersOf(room); return userRepository.usersOf(room);
} }

View File

@ -8,6 +8,7 @@ import ru.dragonestia.picker.api.repository.type.UserIdentifier;
import ru.dragonestia.picker.api.util.IdentifierValidator; import ru.dragonestia.picker.api.util.IdentifierValidator;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -30,7 +31,7 @@ public class NamingValidator {
return IdentifierValidator.forUser(input); return IdentifierValidator.forUser(input);
} }
public List<User> validateUserIds(List<String> input) throws InvalidUsernamesException { public void validateUserIds(Collection<String> input) throws InvalidUsernamesException {
var users = new LinkedList<User>(); var users = new LinkedList<User>();
var invalid = new LinkedList<String>(); var invalid = new LinkedList<String>();
@ -44,9 +45,7 @@ public class NamingValidator {
} }
if (!invalid.isEmpty()) { if (!invalid.isEmpty()) {
throw new InvalidUsernamesException(input, invalid); throw new InvalidUsernamesException(input.stream().toList(), invalid);
} }
return users;
} }
} }

View File

@ -40,7 +40,7 @@ public class LeastPickedTests {
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(PickingArgumentProvider.class) @ArgumentsSource(PickingArgumentProvider.class)
void testPicking(String expectedRoomId, int usersAmount) { 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()); Assertions.assertTrue(roomOpt.isPresent());
var room = roomOpt.get(); var room = roomOpt.get();
@ -73,7 +73,7 @@ public class LeastPickedTests {
@Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
@Test @Test
void testNoOneRoomExpected() { // Take 9 users. expected none result 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()); Assertions.assertTrue(roomOpt.isEmpty());
} }
} }

View File

@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.config.FillingNodesConfig; import ru.dragonestia.picker.config.FillingNodesConfig;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.RoomRepository;
@ -39,7 +38,7 @@ public class RoundRobinTests {
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(PickingArgumentProvider.class) @ArgumentsSource(PickingArgumentProvider.class)
void testPicking(String expectedRoomId, int usersAmount) { 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()); Assertions.assertTrue(roomOpt.isPresent());
var room = roomOpt.get(); var room = roomOpt.get();
@ -69,7 +68,7 @@ public class RoundRobinTests {
@Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
@Test @Test
void testNoOneRoomExpected() { // Take 9 users. expected none result 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()); Assertions.assertTrue(roomOpt.isEmpty());
} }
} }

View File

@ -40,7 +40,7 @@ public class SequentialFillingTests {
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(PickingArgumentProvider.class) @ArgumentsSource(PickingArgumentProvider.class)
void testPicking(String expectedRoomId, int usersAmount) { 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()); Assertions.assertTrue(roomOpt.isPresent());
var room = roomOpt.get(); var room = roomOpt.get();
@ -70,7 +70,7 @@ public class SequentialFillingTests {
@Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @Timeout(value = 1, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
@Test @Test
void testNoOneRoomExpected() { // Take 9 users. expected none result 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()); Assertions.assertTrue(roomOpt.isEmpty());
} }
} }