Refactored model Room

This commit is contained in:
Andrey Terentev 2024-03-13 16:03:30 +07:00 committed by Andrey Terentev
parent 385dfea98b
commit dfd6dbaf17
14 changed files with 114 additions and 73 deletions

View File

@ -11,6 +11,9 @@ import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import ru.dragonestia.picker.api.model.node.PickingMethod; import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.interceptor.DebugInterceptor; import ru.dragonestia.picker.interceptor.DebugInterceptor;
import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Node;
@ -42,9 +45,9 @@ public class TestConfig implements WebMvcConfigurer {
@Bean @Bean
void createNodes() { void createNodes() {
createNodeWithContent(new Node("game-servers", PickingMethod.ROUND_ROBIN, false)); createNodeWithContent(new Node(NodeIdentifier.of("game-servers"), PickingMethod.ROUND_ROBIN, false));
createNodeWithContent(new Node("game-lobbies", PickingMethod.LEAST_PICKED, false)); createNodeWithContent(new Node(NodeIdentifier.of("game-lobbies"), PickingMethod.LEAST_PICKED, false));
createNodeWithContent(new Node("hub", PickingMethod.SEQUENTIAL_FILLING, false)); createNodeWithContent(new Node(NodeIdentifier.of("hub"), PickingMethod.SEQUENTIAL_FILLING, false));
} }
@SneakyThrows @SneakyThrows
@ -54,7 +57,7 @@ public class TestConfig implements WebMvcConfigurer {
for (int i = 1; i <= 5; i++) { for (int i = 1; i <= 5; i++) {
var slots = 5 * i; var slots = 5 * i;
var room = Room.create("test-" + i, node, SlotLimit.of(slots), json.writeValueAsString(generatePayload()), false); var room = new Room(RoomIdentifier.of("test-" + i), node, slots, json.writeValueAsString(generatePayload()), false);
roomRepository.create(room); roomRepository.create(room);
for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) { for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) {
@ -64,7 +67,7 @@ public class TestConfig implements WebMvcConfigurer {
} }
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
var room = Room.create(randomUUID().toString(), node, SlotLimit.unlimited(), json.writeValueAsString(generatePayload()), false); var room = new Room(RoomIdentifier.of(randomUUID().toString()), node, IRoom.UNLIMITED_SLOTS, json.writeValueAsString(generatePayload()), false);
room.setLocked((i & 1) == 0); room.setLocked((i & 1) == 0);
roomRepository.create(room); roomRepository.create(room);
} }

View File

@ -11,6 +11,7 @@ import ru.dragonestia.picker.api.model.node.PickingMethod;
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.model.Node; import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.NodeService;
import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.service.RoomService;
@ -45,7 +46,7 @@ public class NodeController {
@Parameter(description = "Picking method method") @RequestParam(name = "method") PickingMethod method, @Parameter(description = "Picking method method") @RequestParam(name = "method") PickingMethod method,
@Parameter(description = "Save node") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist @Parameter(description = "Save node") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist
) { ) {
nodeService.create(new Node(nodeId, method, persist)); nodeService.create(new Node(NodeIdentifier.of(nodeId), method, persist));
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }

View File

@ -11,8 +11,8 @@ 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.RoomInfoResponse; import ru.dragonestia.picker.api.repository.response.RoomInfoResponse;
import ru.dragonestia.picker.api.repository.response.RoomListResponse; import ru.dragonestia.picker.api.repository.response.RoomListResponse;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.type.SlotLimit;
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.util.DetailsParser; import ru.dragonestia.picker.util.DetailsParser;
@ -54,7 +54,7 @@ public class RoomController {
@Parameter(description = "Save room") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist @Parameter(description = "Save room") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist
) { ) {
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
var room = Room.create(roomId, node, SlotLimit.of(slots), payload, persist); var room = new Room(RoomIdentifier.of(roomId), node, slots, payload, persist);
room.setLocked(locked); room.setLocked(locked);
roomService.create(room); roomService.create(room);

View File

@ -42,7 +42,7 @@ public class UserRoomController {
var room = getNodeAndRoom(nodeId, roomId).room(); var room = getNodeAndRoom(nodeId, roomId).room();
var users = userService.getRoomUsersWithDetailsResponse(room, detailsParser.parseUserDetails(detailsSeq)); var users = userService.getRoomUsersWithDetailsResponse(room, detailsParser.parseUserDetails(detailsSeq));
return ResponseEntity.ok(new RoomUserListResponse(room.getSlots().getSlots(), users.size(), users)); return ResponseEntity.ok(new RoomUserListResponse(room.getMaxSlots(), users.size(), users));
} }
@Operation(summary = "Link users with room") @Operation(summary = "Link users with room")
@ -56,7 +56,7 @@ 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 = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
var usedSlots = userService.linkUsersWithRoom(room, users, force); var usedSlots = userService.linkUsersWithRoom(room, users, force);
return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getSlots().getSlots())); return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getMaxSlots()));
} }
@Operation(summary = "Unlink users from room") @Operation(summary = "Unlink users from room")

View File

@ -6,6 +6,7 @@ import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.api.model.node.NodeDetails; import ru.dragonestia.picker.api.model.node.NodeDetails;
import ru.dragonestia.picker.api.model.node.PickingMethod; import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.api.model.node.ResponseNode; import ru.dragonestia.picker.api.model.node.ResponseNode;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
public class Node implements INode { public class Node implements INode {
@ -13,8 +14,8 @@ public class Node implements INode {
private final PickingMethod pickingMethod; private final PickingMethod pickingMethod;
private final boolean persist; private final boolean persist;
public Node(@NotNull String identifier, @NotNull PickingMethod pickingMethod, boolean persist) { public Node(@NotNull NodeIdentifier identifier, @NotNull PickingMethod pickingMethod, boolean persist) {
this.identifier = identifier; this.identifier = identifier.getValue();
this.pickingMethod = pickingMethod; this.pickingMethod = pickingMethod;
this.persist = persist; this.persist = persist;
} }

View File

@ -1,42 +1,86 @@
package ru.dragonestia.picker.model; package ru.dragonestia.picker.model;
import lombok.AccessLevel; import org.jetbrains.annotations.NotNull;
import lombok.Getter; import org.jetbrains.annotations.Nullable;
import lombok.RequiredArgsConstructor; import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.model.room.ResponseRoom; import ru.dragonestia.picker.api.model.room.ResponseRoom;
import ru.dragonestia.picker.api.model.room.RoomDetails;
import ru.dragonestia.picker.api.model.room.ShortResponseRoom; import ru.dragonestia.picker.api.model.room.ShortResponseRoom;
import ru.dragonestia.picker.model.type.SlotLimit; import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import java.util.HashMap; public class Room implements IRoom {
@Getter private final String identifier;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) private final String nodeIdentifier;
public class Room { private final int slots;
private final String id;
private final String nodeId;
private final SlotLimit slots;
private final String payload; private final String payload;
private final boolean persist; private final boolean persist;
private boolean locked = false; private boolean locked = false;
public static Room create(String roomId, Node node, SlotLimit limit, String payload, boolean persist) { public Room(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean persist) {
return new Room(roomId, node.getIdentifier(), limit, payload, persist); this.identifier = identifier.getValue();
this.nodeIdentifier = node.getIdentifier();
this.slots = slots;
this.payload = payload;
this.persist = persist;
}
@Override
public @NotNull String getIdentifier() {
return identifier;
}
@Override
public @NotNull String getNodeIdentifier() {
return nodeIdentifier;
}
@Override
public int getMaxSlots() {
return slots;
}
@Override
public boolean isLocked() {
return locked;
} }
public void setLocked(boolean value) { public void setLocked(boolean value) {
locked = value; locked = value;
} }
@Override
public @NotNull Boolean isPersist() {
return persist;
}
@Override
public @NotNull String getPayload() {
return payload;
}
@Override
public @Nullable String getDetail(@NotNull RoomDetails detail) {
throw new UnsupportedOperationException();
}
public boolean isAvailable(int usedSlots, int requiredSlots) { public boolean isAvailable(int usedSlots, int requiredSlots) {
if (locked) return false; if (locked) return false;
if (slots.isUnlimited()) return true; if (hasUnlimitedSlots()) return true;
return slots.getSlots() >= usedSlots + requiredSlots; return slots >= usedSlots + requiredSlots;
}
public @NotNull ResponseRoom toResponseObject() {
return new ResponseRoom(identifier, nodeIdentifier, slots, locked, payload);
}
public @NotNull ShortResponseRoom toShortResponseObject() {
return new ShortResponseRoom(identifier, nodeIdentifier, slots, locked);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return id.hashCode(); return identifier.hashCode();
} }
@Override @Override
@ -44,16 +88,8 @@ public class Room {
if (object == this) return true; if (object == this) return true;
if (object == null) return false; if (object == null) return false;
if (object instanceof Room other) { if (object instanceof Room other) {
return id.equals(other.id); return identifier.equals(other.identifier);
} }
return false; return false;
} }
public ResponseRoom toResponseObject() {
return new ResponseRoom(id, nodeId, slots.getSlots(), locked, payload);
}
public ShortResponseRoom toShortResponseObject() {
return new ShortResponseRoom(id, nodeId, slots.getSlots(), locked);
}
} }

View File

@ -24,11 +24,11 @@ public class RoomRepositoryImpl implements RoomRepository {
@Override @Override
public void create(Room room) throws RoomAlreadyExistException { public void create(Room room) throws RoomAlreadyExistException {
var nodeId = room.getNodeId(); var nodeId = room.getNodeIdentifier();
synchronized (node2roomsMap) { synchronized (node2roomsMap) {
var node = node2roomsMap.keySet().stream() var node = node2roomsMap.keySet().stream()
.filter(n -> room.getNodeId().equals(n.getIdentifier())) .filter(n -> room.getNodeIdentifier().equals(n.getIdentifier()))
.findFirst(); .findFirst();
if (node.isEmpty()) { if (node.isEmpty()) {
@ -36,19 +36,19 @@ public class RoomRepositoryImpl implements RoomRepository {
} }
var rooms = node2roomsMap.get(node.get()); var rooms = node2roomsMap.get(node.get());
if (rooms.containsKey(room.getId())) { if (rooms.containsKey(room.getIdentifier())) {
throw new RoomAlreadyExistException(room.getNodeId(), room.getId()); throw new RoomAlreadyExistException(room.getNodeIdentifier(), room.getIdentifier());
} }
rooms.put(room.getId(), new RoomContainer(room, new AtomicInteger(0))); rooms.put(room.getIdentifier(), new RoomContainer(room, new AtomicInteger(0)));
pickerRepository.find(room.getNodeId()).add(room); pickerRepository.find(room.getNodeIdentifier()).add(room);
} }
} }
@Override @Override
public void remove(Room room) { public void remove(Room room) {
var nodeId = room.getNodeId(); var nodeId = room.getNodeIdentifier();
var node = node2roomsMap.keySet().stream() var node = node2roomsMap.keySet().stream()
.filter(n -> room.getNodeId().equals(n.getIdentifier())) .filter(n -> room.getNodeIdentifier().equals(n.getIdentifier()))
.findFirst(); .findFirst();
synchronized (node2roomsMap) { synchronized (node2roomsMap) {
@ -56,8 +56,8 @@ public class RoomRepositoryImpl implements RoomRepository {
throw new NodeNotFoundException("Node '" + nodeId + "' does not exist"); throw new NodeNotFoundException("Node '" + nodeId + "' does not exist");
} }
node2roomsMap.get(node.get()).remove(room.getId()); node2roomsMap.get(node.get()).remove(room.getIdentifier());
pickerRepository.find(room.getNodeId()).remove(room); pickerRepository.find(room.getNodeIdentifier()).remove(room);
} }
userRepository.onRemoveRoom(room); userRepository.onRemoveRoom(room);
@ -100,7 +100,7 @@ public class RoomRepositoryImpl implements RoomRepository {
Optional<RoomContainer> container = room == null? Optional<RoomContainer> container = room == null?
Optional.empty() : Optional.empty() :
Optional.of(node2roomsMap.get(node).get(room.getId())); Optional.of(node2roomsMap.get(node).get(room.getIdentifier()));
if (container.isPresent()) { if (container.isPresent()) {
var cont = container.get(); var cont = container.get();

View File

@ -26,10 +26,10 @@ public class UserRepositoryImpl implements UserRepository {
var result = new HashMap<User, Boolean>(); var result = new HashMap<User, Boolean>();
synchronized (usersMap) { synchronized (usersMap) {
var path = new NodeRoomPath(room.getNodeId(), room.getId()); var path = new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier());
var usersSet = roomUsers.getOrDefault(path, new HashSet<>()); var usersSet = roomUsers.getOrDefault(path, new HashSet<>());
if (force || room.getSlots().isUnlimited()) { if (force || room.hasUnlimitedSlots()) {
users.forEach(user -> result.put(user, true)); users.forEach(user -> result.put(user, true));
} else { } else {
for (var user : users) { for (var user : users) {
@ -37,8 +37,8 @@ public class UserRepositoryImpl implements UserRepository {
result.put(user, !set.contains(room)); result.put(user, !set.contains(room));
} }
if (room.getSlots().getSlots() < usersSet.size() + users.size()) { if (room.getMaxSlots() < usersSet.size() + users.size()) {
throw new RoomAreFullException(room.getNodeId(), room.getId()); throw new RoomAreFullException(room.getNodeIdentifier(), room.getIdentifier());
} }
} }
@ -51,7 +51,7 @@ public class UserRepositoryImpl implements UserRepository {
usersSet.addAll(users); usersSet.addAll(users);
roomUsers.put(path, usersSet); roomUsers.put(path, usersSet);
var picker = nodeId2PickerModeCache.get(room.getNodeId()); var picker = nodeId2PickerModeCache.get(room.getNodeIdentifier());
if (picker instanceof LeastPickedPicker leastPickedPicker) { if (picker instanceof LeastPickedPicker leastPickedPicker) {
leastPickedPicker.updateUsersAmount(room, roomUsers.get(path).size()); leastPickedPicker.updateUsersAmount(room, roomUsers.get(path).size());
} }
@ -75,7 +75,7 @@ public class UserRepositoryImpl implements UserRepository {
} }
}); });
var path = new NodeRoomPath(room.getNodeId(), room.getId()); var path = new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier());
var set = roomUsers.getOrDefault(path, new HashSet<>()); var set = roomUsers.getOrDefault(path, new HashSet<>());
set.removeAll(users); set.removeAll(users);
if (set.isEmpty()) { if (set.isEmpty()) {
@ -84,7 +84,7 @@ public class UserRepositoryImpl implements UserRepository {
roomUsers.put(path, set); roomUsers.put(path, set);
} }
var picker = nodeId2PickerModeCache.get(room.getNodeId()); var picker = nodeId2PickerModeCache.get(room.getNodeIdentifier());
if (picker instanceof LeastPickedPicker leastPickedPicker) { if (picker instanceof LeastPickedPicker leastPickedPicker) {
leastPickedPicker.updateUsersAmount(room, set.size()); leastPickedPicker.updateUsersAmount(room, set.size());
} }
@ -114,7 +114,7 @@ public class UserRepositoryImpl implements UserRepository {
@Override @Override
public List<User> usersOf(Room room) { public List<User> usersOf(Room room) {
synchronized (usersMap) { synchronized (usersMap) {
return roomUsers.getOrDefault(new NodeRoomPath(room.getNodeId(), room.getId()), new HashSet<>()) return roomUsers.getOrDefault(new NodeRoomPath(room.getNodeIdentifier(), room.getIdentifier()), new HashSet<>())
.stream() .stream()
.toList(); .toList();
} }

View File

@ -27,7 +27,7 @@ public class LeastPickedPicker implements RoomPicker {
@Override @Override
public void remove(Room room) { public void remove(Room room) {
synchronized (map) { synchronized (map) {
map.removeById(room.getId()); map.removeById(room.getIdentifier());
} }
} }
@ -50,7 +50,7 @@ public class LeastPickedPicker implements RoomPicker {
public void updateUsersAmount(Room room, int users) { public void updateUsersAmount(Room room, int users) {
synchronized (map) { synchronized (map) {
map.updateItem(room.getId(), prevValue -> users); map.updateItem(room.getIdentifier(), prevValue -> users);
} }
} }

View File

@ -20,7 +20,7 @@ public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item, Dy
@Override @Override
public String getId() { public String getId() {
return room.getId(); return room.getIdentifier();
} }
@Override @Override
@ -30,7 +30,7 @@ public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item, Dy
@Override @Override
public int maxUnits() { public int maxUnits() {
return room.getSlots().getSlots(); return room.getMaxSlots();
} }
@Override @Override

View File

@ -29,7 +29,7 @@ public class RoundRobinPicker implements RoomPicker {
@Override @Override
public void remove(Room room) { public void remove(Room room) {
synchronized (list) { synchronized (list) {
list.removeById(room.getId()); list.removeById(room.getIdentifier());
} }
} }

View File

@ -21,14 +21,14 @@ public class SequentialFillingPicker implements RoomPicker {
@Override @Override
public void add(Room room) { public void add(Room room) {
synchronized (wrappers) { synchronized (wrappers) {
wrappers.put(room.getId(), new RoomWrapper(room, () -> userRepository.usersOf(room).size())); wrappers.put(room.getIdentifier(), new RoomWrapper(room, () -> userRepository.usersOf(room).size()));
} }
} }
@Override @Override
public void remove(Room room) { public void remove(Room room) {
synchronized (wrappers) { synchronized (wrappers) {
wrappers.remove(room.getId()); wrappers.remove(room.getIdentifier());
} }
} }

View File

@ -41,11 +41,11 @@ public class RoomServiceImpl implements RoomService {
@Override @Override
public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException, NotPersistedNodeException { public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException, NotPersistedNodeException {
namingValidator.validateRoomId(room.getNodeId(), room.getId()); namingValidator.validateRoomId(room.getNodeIdentifier(), room.getIdentifier());
var node = nodeRepository.find(room.getNodeId()).orElseThrow(() -> new NodeNotFoundException(room.getNodeId())); var node = nodeRepository.find(room.getNodeIdentifier()).orElseThrow(() -> new NodeNotFoundException(room.getNodeIdentifier()));
if (!node.isPersist() && room.isPersist()) { if (!node.isPersist() && room.isPersist()) {
throw new NotPersistedNodeException(node.getIdentifier(), room.getId()); throw new NotPersistedNodeException(node.getIdentifier(), room.getIdentifier());
} }
roomRepository.create(room); roomRepository.create(room);
@ -84,10 +84,10 @@ public class RoomServiceImpl implements RoomService {
var roomUsers = userRepository.usersOf(room); var roomUsers = userRepository.usersOf(room);
return new PickedRoomResponse( return new PickedRoomResponse(
room.getNodeId(), room.getNodeIdentifier(),
room.getId(), room.getIdentifier(),
room.getPayload(), room.getPayload(),
room.getSlots().getSlots(), room.getMaxSlots(),
roomUsers.size(), roomUsers.size(),
room.isLocked(), room.isLocked(),
roomUsers.stream().map(User::id).collect(Collectors.toSet()) roomUsers.stream().map(User::id).collect(Collectors.toSet())

View File

@ -88,7 +88,7 @@ public class FileStorageImpl implements NodeAndRoomStorage {
@Override @Override
public void saveRoom(Room room) { public void saveRoom(Room room) {
if (!room.isPersist()) return; if (!room.isPersist()) return;
var roomFile = new File(path + "/rooms/" + room.getNodeId() + "." + room.getId() + ".json"); var roomFile = new File("%s/rooms/%s.%s.json".formatted(path, room.getNodeIdentifier(), room.getIdentifier()));
var writer = objectMapper.writer(); var writer = objectMapper.writer();
try { try {
@ -101,8 +101,8 @@ public class FileStorageImpl implements NodeAndRoomStorage {
@Override @Override
public void removeRoom(Room room) { public void removeRoom(Room room) {
if (!room.isPersist()) return; if (!room.isPersist()) return;
new File(path + "/rooms/" + room.getNodeId() + "." + room.getId() + ".json").delete(); new File("%s/rooms/%s.%s.json".formatted(path, room.getNodeIdentifier(), room.getIdentifier())).delete();
log.info("Removed room '%s/%s' from disk storage".formatted(room.getNodeId(), room.getId())); log.info("Removed room '%s/%s' from disk storage".formatted(room.getNodeIdentifier(), room.getIdentifier()));
} }
} }