Refactored repositories, deleted shit code
This commit is contained in:
parent
087a67f021
commit
8b655bfb4b
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -58,4 +58,9 @@ public class Node implements INode {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{Node id='%s'}".formatted(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Room> delete(Node node);
|
||||
void delete(Node node);
|
||||
|
||||
Optional<Node> find(String nodeId);
|
||||
Optional<Node> findById(String nodeId);
|
||||
|
||||
List<Node> all();
|
||||
}
|
||||
|
||||
@ -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<Room> find(Node node, String identifier);
|
||||
|
||||
List<Room> all(Node node);
|
||||
Collection<Room> all(Node node);
|
||||
|
||||
Optional<Room> pickFree(Node node, Collection<User> users);
|
||||
|
||||
void onCreateNode(Node node);
|
||||
|
||||
List<Room> onRemoveNode(Node node);
|
||||
Room pick(Node node, Set<User> users) throws NoRoomsAvailableException;
|
||||
}
|
||||
|
||||
@ -10,17 +10,15 @@ import java.util.Map;
|
||||
|
||||
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);
|
||||
|
||||
List<Room> findAllLinkedUserRooms(User user);
|
||||
Collection<Room> findAllLinkedUserRooms(User user);
|
||||
|
||||
void onRemoveRoom(Room room);
|
||||
Collection<User> usersOf(Room room);
|
||||
|
||||
List<User> usersOf(Room room);
|
||||
|
||||
List<User> search(String input);
|
||||
Collection<User> search(String input);
|
||||
|
||||
int countAllUsers();
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<String, Node> 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<Room> 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<Node> find(String nodeId) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return nodeMap.containsKey(nodeId)? Optional.of(nodeMap.get(nodeId)) : Optional.empty();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
public Optional<Node> findById(String nodeId) {
|
||||
return containerRepository.findById(nodeId).map(NodeContainer::getNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Node> all() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return nodeMap.values().stream().toList();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
return containerRepository.all().stream().map(NodeContainer::getNode).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Node, Rooms> 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<Room> 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<Room> 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<Room> all(Node node) {
|
||||
return containerRepository.findById(node.getIdentifier())
|
||||
.orElseThrow(() -> new NodeNotFoundException(node.getIdentifier()))
|
||||
.allRooms()
|
||||
.stream().map(RoomContainer::getRoom).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Room> pickFree(Node node, Collection<User> 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<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();
|
||||
public Room pick(Node node, Set<User> 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<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> {}
|
||||
}
|
||||
|
||||
@ -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<User, Set<Room>> usersMap = new ConcurrentHashMap<>();
|
||||
private final Map<NodeRoomPath, Set<User>> roomUsers = new ConcurrentHashMap<>();
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
|
||||
private final ContainerRepository containerRepository;
|
||||
|
||||
private final Map<User, Set<Room>> 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<User> users, boolean force) throws RoomAreFullException {
|
||||
var toAdd = new HashSet<User>();
|
||||
public void linkWithRoom(Room room, Collection<User> 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());
|
||||
}
|
||||
}
|
||||
|
||||
users.forEach(user -> {
|
||||
var set = usersMap.getOrDefault(user, new HashSet<>());
|
||||
if (!set.contains(room)) {
|
||||
toAdd.add(user);
|
||||
for (var user: users) {
|
||||
var set = userRooms.computeIfAbsent(user, k -> new HashSet<>());
|
||||
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<User> 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);
|
||||
if (set.isEmpty()) userRooms.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
|
||||
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<Room> findAllLinkedUserRooms(User user) {
|
||||
return Collections.unmodifiableSet(userRooms.get(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> 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<User> usersOf(Room room) {
|
||||
return getRoomContainer(room).allUsers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> 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<User> 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<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>();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<RoomWrapper> 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<User> users) {
|
||||
public RoomContainer pick(Collection<User> users) {
|
||||
RoomWrapper wrapper;
|
||||
|
||||
synchronized (map) {
|
||||
|
||||
@ -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<Room, User> {
|
||||
public interface RoomPicker extends Picker<RoomContainer, User> {
|
||||
|
||||
PickingMethod getPickingMode();
|
||||
}
|
||||
|
||||
@ -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<Room>, QueuedLinkedList.Item, DynamicSortedMap.Item {
|
||||
public class RoomWrapper implements ItemWrapper<RoomContainer>, QueuedLinkedList.Item, DynamicSortedMap.Item {
|
||||
|
||||
private final Room room;
|
||||
private final Supplier<Integer> userCountSupplier;
|
||||
private final RoomContainer container;
|
||||
private Consumer<Integer> setter;
|
||||
|
||||
public RoomWrapper(Room room, Supplier<Integer> 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
|
||||
|
||||
@ -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<RoomWrapper> 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<User> users) {
|
||||
public RoomContainer pick(Collection<User> users) {
|
||||
int amount = users.size();
|
||||
RoomWrapper wrapper;
|
||||
|
||||
|
||||
@ -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<String, RoomWrapper> 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<User> users) {
|
||||
public RoomContainer pick(Collection<User> users) {
|
||||
int amount = users.size();
|
||||
|
||||
synchronized (wrappers) {
|
||||
|
||||
@ -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> {}
|
||||
}
|
||||
@ -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<Room> find(Node node, String roomId);
|
||||
|
||||
List<Room> all(Node node);
|
||||
Collection<Room> all(Node node);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import java.util.Set;
|
||||
|
||||
public interface UserService {
|
||||
|
||||
List<Room> getUserRooms(User user);
|
||||
Collection<Room> getUserRooms(User user);
|
||||
|
||||
List<ShortResponseRoom> getUserRoomsWithDetails(User user, Set<RoomDetails> details);
|
||||
|
||||
@ -22,7 +22,7 @@ public interface UserService {
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@ -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<Node> find(String nodeId) {
|
||||
return nodeRepository.find(nodeId);
|
||||
return nodeRepository.findById(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Room> all(Node node) {
|
||||
public Collection<Room> all(Node node) {
|
||||
return roomRepository.all(node);
|
||||
}
|
||||
|
||||
@ -78,9 +75,8 @@ public class RoomServiceImpl implements RoomService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickedRoomResponse pickAvailable(Node node, List<User> 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<User> users) {
|
||||
var room = roomRepository.pick(node, users);
|
||||
var roomUsers = userRepository.usersOf(room);
|
||||
|
||||
return new PickedRoomResponse(
|
||||
|
||||
@ -23,7 +23,7 @@ public class UserServiceImpl implements UserService {
|
||||
private final DetailsExtractor detailsExtractor;
|
||||
|
||||
@Override
|
||||
public List<Room> getUserRooms(User user) {
|
||||
public Collection<Room> getUserRooms(User user) {
|
||||
return userRepository.findAllLinkedUserRooms(user);
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> getRoomUsers(Room room) {
|
||||
public Collection<User> getRoomUsers(Room room) {
|
||||
return userRepository.usersOf(room);
|
||||
}
|
||||
|
||||
|
||||
@ -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<User> validateUserIds(List<String> input) throws InvalidUsernamesException {
|
||||
public void validateUserIds(Collection<String> input) throws InvalidUsernamesException {
|
||||
var users = new LinkedList<User>();
|
||||
var invalid = new LinkedList<String>();
|
||||
|
||||
@ -44,9 +45,7 @@ public class NamingValidator {
|
||||
}
|
||||
|
||||
if (!invalid.isEmpty()) {
|
||||
throw new InvalidUsernamesException(input, invalid);
|
||||
}
|
||||
|
||||
return users;
|
||||
throw new InvalidUsernamesException(input.stream().toList(), invalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user