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.WebMvcConfigurer;
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.model.Room;
import ru.dragonestia.picker.model.Node;
@ -42,9 +45,9 @@ public class TestConfig implements WebMvcConfigurer {
@Bean
void createNodes() {
createNodeWithContent(new Node("game-servers", PickingMethod.ROUND_ROBIN, false));
createNodeWithContent(new Node("game-lobbies", PickingMethod.LEAST_PICKED, false));
createNodeWithContent(new Node("hub", PickingMethod.SEQUENTIAL_FILLING, false));
createNodeWithContent(new Node(NodeIdentifier.of("game-servers"), PickingMethod.ROUND_ROBIN, false));
createNodeWithContent(new Node(NodeIdentifier.of("game-lobbies"), PickingMethod.LEAST_PICKED, false));
createNodeWithContent(new Node(NodeIdentifier.of("hub"), PickingMethod.SEQUENTIAL_FILLING, false));
}
@SneakyThrows
@ -54,7 +57,7 @@ public class TestConfig implements WebMvcConfigurer {
for (int i = 1; i <= 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);
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++) {
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);
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.NodeListResponse;
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.service.NodeService;
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 = "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();
}

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.repository.response.RoomInfoResponse;
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.type.SlotLimit;
import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.service.NodeService;
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
) {
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);
roomService.create(room);

View File

@ -42,7 +42,7 @@ public class UserRoomController {
var room = getNodeAndRoom(nodeId, roomId).room();
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")
@ -56,7 +56,7 @@ public class UserRoomController {
var room = getNodeAndRoom(nodeId, roomId).room();
var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
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")

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

View File

@ -1,42 +1,86 @@
package ru.dragonestia.picker.model;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.dragonestia.picker.api.model.room.IRoom;
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.model.type.SlotLimit;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import java.util.HashMap;
public class Room implements IRoom {
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Room {
private final String id;
private final String nodeId;
private final SlotLimit slots;
private final String identifier;
private final String nodeIdentifier;
private final int slots;
private final String payload;
private final boolean persist;
private boolean locked = false;
public static Room create(String roomId, Node node, SlotLimit limit, String payload, boolean persist) {
return new Room(roomId, node.getIdentifier(), limit, payload, persist);
public Room(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean 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) {
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) {
if (locked) return false;
if (slots.isUnlimited()) return true;
return slots.getSlots() >= usedSlots + requiredSlots;
if (hasUnlimitedSlots()) return true;
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
public int hashCode() {
return id.hashCode();
return identifier.hashCode();
}
@Override
@ -44,16 +88,8 @@ public class Room {
if (object == this) return true;
if (object == null) return false;
if (object instanceof Room other) {
return id.equals(other.id);
return identifier.equals(other.identifier);
}
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
public void create(Room room) throws RoomAlreadyExistException {
var nodeId = room.getNodeId();
var nodeId = room.getNodeIdentifier();
synchronized (node2roomsMap) {
var node = node2roomsMap.keySet().stream()
.filter(n -> room.getNodeId().equals(n.getIdentifier()))
.filter(n -> room.getNodeIdentifier().equals(n.getIdentifier()))
.findFirst();
if (node.isEmpty()) {
@ -36,19 +36,19 @@ public class RoomRepositoryImpl implements RoomRepository {
}
var rooms = node2roomsMap.get(node.get());
if (rooms.containsKey(room.getId())) {
throw new RoomAlreadyExistException(room.getNodeId(), room.getId());
if (rooms.containsKey(room.getIdentifier())) {
throw new RoomAlreadyExistException(room.getNodeIdentifier(), room.getIdentifier());
}
rooms.put(room.getId(), new RoomContainer(room, new AtomicInteger(0)));
pickerRepository.find(room.getNodeId()).add(room);
rooms.put(room.getIdentifier(), new RoomContainer(room, new AtomicInteger(0)));
pickerRepository.find(room.getNodeIdentifier()).add(room);
}
}
@Override
public void remove(Room room) {
var nodeId = room.getNodeId();
var nodeId = room.getNodeIdentifier();
var node = node2roomsMap.keySet().stream()
.filter(n -> room.getNodeId().equals(n.getIdentifier()))
.filter(n -> room.getNodeIdentifier().equals(n.getIdentifier()))
.findFirst();
synchronized (node2roomsMap) {
@ -56,8 +56,8 @@ public class RoomRepositoryImpl implements RoomRepository {
throw new NodeNotFoundException("Node '" + nodeId + "' does not exist");
}
node2roomsMap.get(node.get()).remove(room.getId());
pickerRepository.find(room.getNodeId()).remove(room);
node2roomsMap.get(node.get()).remove(room.getIdentifier());
pickerRepository.find(room.getNodeIdentifier()).remove(room);
}
userRepository.onRemoveRoom(room);
@ -100,7 +100,7 @@ public class RoomRepositoryImpl implements RoomRepository {
Optional<RoomContainer> container = room == null?
Optional.empty() :
Optional.of(node2roomsMap.get(node).get(room.getId()));
Optional.of(node2roomsMap.get(node).get(room.getIdentifier()));
if (container.isPresent()) {
var cont = container.get();

View File

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

View File

@ -27,7 +27,7 @@ public class LeastPickedPicker implements RoomPicker {
@Override
public void remove(Room room) {
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) {
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
public String getId() {
return room.getId();
return room.getIdentifier();
}
@Override
@ -30,7 +30,7 @@ public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item, Dy
@Override
public int maxUnits() {
return room.getSlots().getSlots();
return room.getMaxSlots();
}
@Override

View File

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

View File

@ -21,14 +21,14 @@ public class SequentialFillingPicker implements RoomPicker {
@Override
public void add(Room room) {
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
public void remove(Room room) {
synchronized (wrappers) {
wrappers.remove(room.getId());
wrappers.remove(room.getIdentifier());
}
}

View File

@ -41,11 +41,11 @@ public class RoomServiceImpl implements RoomService {
@Override
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()) {
throw new NotPersistedNodeException(node.getIdentifier(), room.getId());
throw new NotPersistedNodeException(node.getIdentifier(), room.getIdentifier());
}
roomRepository.create(room);
@ -84,10 +84,10 @@ public class RoomServiceImpl implements RoomService {
var roomUsers = userRepository.usersOf(room);
return new PickedRoomResponse(
room.getNodeId(),
room.getId(),
room.getNodeIdentifier(),
room.getIdentifier(),
room.getPayload(),
room.getSlots().getSlots(),
room.getMaxSlots(),
roomUsers.size(),
room.isLocked(),
roomUsers.stream().map(User::id).collect(Collectors.toSet())

View File

@ -88,7 +88,7 @@ public class FileStorageImpl implements NodeAndRoomStorage {
@Override
public void saveRoom(Room room) {
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();
try {
@ -101,8 +101,8 @@ public class FileStorageImpl implements NodeAndRoomStorage {
@Override
public void removeRoom(Room room) {
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()));
}
}