Renamed Bucket to Room
This commit is contained in:
parent
a8348dcd3d
commit
3de835cc6f
@ -59,8 +59,9 @@ For use api methods from main application you need add http headers with usernam
|
||||
and password (`Room_Picker-Password`).
|
||||
|
||||
Example:
|
||||
|
||||
```http request
|
||||
GET http://localhost:8080/nodes/hub/buckets/test-3/users
|
||||
GET http://localhost:8080/nodes/hub/rooms/test-3/users
|
||||
RoomPicker-User: admin
|
||||
RoomPicker-Password: admin
|
||||
```
|
||||
@ -6,8 +6,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class LoadBalancerApplication {
|
||||
|
||||
public record Test(String text) {}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LoadBalancerApplication.class, args);
|
||||
}
|
||||
|
||||
@ -8,12 +8,12 @@ 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.interceptor.DebugInterceptor;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.LoadBalancingMethod;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||
import ru.dragonestia.picker.repository.BucketRepository;
|
||||
import ru.dragonestia.picker.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.repository.NodeRepository;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
|
||||
@ -27,7 +27,7 @@ import java.util.UUID;
|
||||
public class TestConfig implements WebMvcConfigurer {
|
||||
|
||||
private final NodeRepository nodeRepository;
|
||||
private final BucketRepository bucketRepository;
|
||||
private final RoomRepository roomRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final Random rand = new Random(0);
|
||||
@ -39,29 +39,29 @@ public class TestConfig implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
void createNodes() {
|
||||
createNodeWithContent(new Node("game-servers", LoadBalancingMethod.ROUND_ROBIN));
|
||||
createNodeWithContent(new Node("game-lobbies", LoadBalancingMethod.LEAST_PICKED));
|
||||
createNodeWithContent(new Node("hub", LoadBalancingMethod.SEQUENTIAL_FILLING));
|
||||
createNodeWithContent(new Node("game-servers", PickingMode.ROUND_ROBIN));
|
||||
createNodeWithContent(new Node("game-lobbies", PickingMode.LEAST_PICKED));
|
||||
createNodeWithContent(new Node("hub", PickingMode.SEQUENTIAL_FILLING));
|
||||
}
|
||||
|
||||
private void createNodeWithContent(Node node) {
|
||||
nodeRepository.createNode(node);
|
||||
nodeRepository.create(node);
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
var slots = 5 * i;
|
||||
var bucket = Bucket.create("test-" + i, node, SlotLimit.of(slots), "Some payload");
|
||||
bucketRepository.createBucket(bucket);
|
||||
var room = Room.create("test-" + i, node, SlotLimit.of(slots), "Some payload");
|
||||
roomRepository.create(room);
|
||||
|
||||
for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) {
|
||||
var user = new User("test-user-" + rand.nextInt(20));
|
||||
userRepository.linkWithBucket(bucket, List.of(user), false);
|
||||
userRepository.linkWithRoom(room, List.of(user), false);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
var bucket = Bucket.create(randomUUID().toString(), node, SlotLimit.unlimited(), "Some payload");
|
||||
bucket.setLocked((i & 1) == 0);
|
||||
bucketRepository.createBucket(bucket);
|
||||
var room = Room.create(randomUUID().toString(), node, SlotLimit.unlimited(), "Some payload");
|
||||
room.setLocked((i & 1) == 0);
|
||||
roomRepository.create(room);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
package ru.dragonestia.picker.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.dragonestia.picker.controller.response.BucketInfoResponse;
|
||||
import ru.dragonestia.picker.controller.response.BucketListResponse;
|
||||
import ru.dragonestia.picker.controller.response.BucketRegisterResponse;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||
import ru.dragonestia.picker.service.BucketService;
|
||||
import ru.dragonestia.picker.service.NodeService;
|
||||
import ru.dragonestia.picker.util.NamingValidator;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Log4j2
|
||||
@RestController
|
||||
@RequestMapping("/nodes/{nodeIdentifier}/buckets")
|
||||
@RequiredArgsConstructor
|
||||
public class BucketController {
|
||||
|
||||
private final NodeService nodeService;
|
||||
private final BucketService bucketService;
|
||||
|
||||
@GetMapping
|
||||
ResponseEntity<BucketListResponse> allBuckets(@PathVariable(name = "nodeIdentifier") String nodeId) {
|
||||
var nodeOpt = nodeService.findNode(nodeId);
|
||||
return nodeOpt.map(node -> ResponseEntity.ok(new BucketListResponse(nodeId,
|
||||
bucketService.allBuckets(node).stream()
|
||||
.map(bucket -> new BucketListResponse.BucketDTO(bucket.getIdentifier(), bucket.getSlots().getSlots(), bucket.isLocked()))
|
||||
.toList()
|
||||
))).orElseGet(() -> ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
ResponseEntity<BucketRegisterResponse> registerBucket(@PathVariable(name = "nodeIdentifier") String nodeId,
|
||||
@RequestParam(name = "identifier") String bucketId,
|
||||
@RequestParam(name = "slots") int slots,
|
||||
@RequestParam(name = "payload") String payload,
|
||||
@RequestParam(name = "locked", defaultValue = "false") boolean locked) {
|
||||
|
||||
var nodeOpt = nodeService.findNode(nodeId);
|
||||
|
||||
if (nodeOpt.isEmpty()) {
|
||||
return ResponseEntity.status(404)
|
||||
.body(new BucketRegisterResponse(false, "Node does not exist"));
|
||||
}
|
||||
|
||||
var bucket = Bucket.create(bucketId, nodeOpt.get(), SlotLimit.of(slots), payload);
|
||||
bucket.setLocked(locked);
|
||||
try {
|
||||
bucketService.createBucket(bucket);
|
||||
return ResponseEntity.ok(new BucketRegisterResponse(true, ""));
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.status(400).body(new BucketRegisterResponse(false, error.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
return ResponseEntity.status(500).body(new BucketRegisterResponse(false, ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{identifier}")
|
||||
ResponseEntity<?> removeBucket(@PathVariable("nodeIdentifier") String nodeId,
|
||||
@PathVariable("identifier") String bucketId) {
|
||||
if (!NamingValidator.validateNodeIdentifier(nodeId) || !NamingValidator.validateBucketIdentifier(bucketId)) {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.findNode(nodeId);
|
||||
nodeOpt.flatMap(node -> bucketService.findBucket(node, bucketId))
|
||||
.ifPresent(bucketService::removeBucket);
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@GetMapping("/{identifier}")
|
||||
ResponseEntity<BucketInfoResponse> info(@PathVariable("nodeIdentifier") String nodeId,
|
||||
@PathVariable("identifier") String bucketId) {
|
||||
if (!NamingValidator.validateNodeIdentifier(nodeId) || !NamingValidator.validateBucketIdentifier(bucketId)) {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.findNode(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var bucketOpt = bucketService.findBucket(Objects.requireNonNull(nodeOpt.get()), bucketId);
|
||||
return bucketOpt.map(bucket -> ResponseEntity.ok(new BucketInfoResponse(bucket)))
|
||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping("/{identifier}/lock")
|
||||
ResponseEntity<Boolean> lockBucket(@PathVariable("nodeIdentifier") String nodeId,
|
||||
@PathVariable("identifier") String bucketId,
|
||||
@RequestParam(name = "state") boolean value) {
|
||||
|
||||
if (!NamingValidator.validateNodeIdentifier(nodeId) || !NamingValidator.validateBucketIdentifier(bucketId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.findNode(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var bucketOpt = bucketService.findBucket(Objects.requireNonNull(nodeOpt.get()), bucketId);
|
||||
if (bucketOpt.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var bucket = bucketOpt.get();
|
||||
bucket.setLocked(value);
|
||||
return ResponseEntity.ok(true);
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ import ru.dragonestia.picker.controller.response.NodeDetailsResponse;
|
||||
import ru.dragonestia.picker.controller.response.NodeListResponse;
|
||||
import ru.dragonestia.picker.controller.response.NodeRegisterResponse;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.type.LoadBalancingMethod;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.service.NodeService;
|
||||
import ru.dragonestia.picker.util.NamingValidator;
|
||||
|
||||
@ -21,15 +21,15 @@ public class NodeController {
|
||||
|
||||
@GetMapping
|
||||
NodeListResponse allNodes() {
|
||||
return new NodeListResponse(nodeService.allNodes());
|
||||
return new NodeListResponse(nodeService.all());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
NodeRegisterResponse registerNode(@RequestParam(name = "identifier") String identifier,
|
||||
@RequestParam(name = "method") LoadBalancingMethod method) {
|
||||
NodeRegisterResponse registerNode(@RequestParam(name = "nodeId") String nodeId,
|
||||
@RequestParam(name = "method") PickingMode method) {
|
||||
|
||||
try {
|
||||
nodeService.createNode(new Node(identifier, method));
|
||||
nodeService.create(new Node(nodeId, method));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return new NodeRegisterResponse(false, ex.getMessage());
|
||||
} catch (Error error) {
|
||||
@ -39,25 +39,25 @@ public class NodeController {
|
||||
return new NodeRegisterResponse(true, "");
|
||||
}
|
||||
|
||||
@GetMapping("/{identifier}")
|
||||
ResponseEntity<NodeDetailsResponse> nodeDetails(@PathVariable("identifier") String identifier) {
|
||||
if (!NamingValidator.validateNodeIdentifier(identifier)) {
|
||||
@GetMapping("/{nodeId}")
|
||||
ResponseEntity<NodeDetailsResponse> nodeDetails(@PathVariable("nodeId") String nodeId) {
|
||||
if (!NamingValidator.validateNodeId(nodeId)) {
|
||||
return new ResponseEntity<>(HttpStatusCode.valueOf(404));
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.findNode(identifier);
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
return nodeOpt.map(node -> ResponseEntity.ok(new NodeDetailsResponse(node)))
|
||||
.orElseGet(() -> new ResponseEntity<>(HttpStatusCode.valueOf(404)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{identifier}")
|
||||
ResponseEntity<?> removeNode(@PathVariable("identifier") String identifier) {
|
||||
if (!NamingValidator.validateNodeIdentifier(identifier)) {
|
||||
@DeleteMapping("/{nodeId}")
|
||||
ResponseEntity<?> removeNode(@PathVariable("nodeId") String nodeId) {
|
||||
if (!NamingValidator.validateNodeId(nodeId)) {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.findNode(identifier);
|
||||
nodeOpt.ifPresent(nodeService::removeNode);
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
nodeOpt.ifPresent(nodeService::remove);
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
package ru.dragonestia.picker.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.dragonestia.picker.controller.response.RoomInfoResponse;
|
||||
import ru.dragonestia.picker.controller.response.RoomListResponse;
|
||||
import ru.dragonestia.picker.controller.response.RoomRegisterResponse;
|
||||
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.NamingValidator;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Log4j2
|
||||
@RestController
|
||||
@RequestMapping("/nodes/{nodeId}/rooms")
|
||||
@RequiredArgsConstructor
|
||||
public class RoomController {
|
||||
|
||||
private final NodeService nodeService;
|
||||
private final RoomService roomService;
|
||||
|
||||
@GetMapping
|
||||
ResponseEntity<RoomListResponse> all(@PathVariable(name = "nodeId") String nodeId) {
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
return nodeOpt.map(node -> ResponseEntity.ok(new RoomListResponse(nodeId,
|
||||
roomService.all(node).stream()
|
||||
.map(room -> new RoomListResponse.RoomDTO(room.getId(), room.getSlots().getSlots(), room.isLocked()))
|
||||
.toList()
|
||||
))).orElseGet(() -> ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
ResponseEntity<RoomRegisterResponse> register(@PathVariable(name = "nodeId") String nodeId,
|
||||
@RequestParam(name = "roomId") String roomId,
|
||||
@RequestParam(name = "slots") int slots,
|
||||
@RequestParam(name = "payload") String payload,
|
||||
@RequestParam(name = "locked", defaultValue = "false") boolean locked) {
|
||||
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
|
||||
if (nodeOpt.isEmpty()) {
|
||||
return ResponseEntity.status(404)
|
||||
.body(new RoomRegisterResponse(false, "Node does not exist"));
|
||||
}
|
||||
|
||||
var room = Room.create(roomId, nodeOpt.get(), SlotLimit.of(slots), payload);
|
||||
room.setLocked(locked);
|
||||
try {
|
||||
roomService.create(room);
|
||||
return ResponseEntity.ok(new RoomRegisterResponse(true, ""));
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.status(400).body(new RoomRegisterResponse(false, error.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
return ResponseEntity.status(500).body(new RoomRegisterResponse(false, ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{roomId}")
|
||||
ResponseEntity<?> remove(@PathVariable("nodeId") String nodeId,
|
||||
@PathVariable("roomId") String roomId) {
|
||||
if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
nodeOpt.flatMap(node -> roomService.find(node, roomId))
|
||||
.ifPresent(roomService::remove);
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@GetMapping("/{roomId}")
|
||||
ResponseEntity<RoomInfoResponse> info(@PathVariable("nodeId") String nodeId,
|
||||
@PathVariable("roomId") String roomId) {
|
||||
if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId);
|
||||
return roomOpt.map(room -> ResponseEntity.ok(new RoomInfoResponse(room)))
|
||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping("/{roomId}/lock")
|
||||
ResponseEntity<Boolean> lockBucket(@PathVariable("nodeId") String nodeId,
|
||||
@PathVariable("roomId") String roomId,
|
||||
@RequestParam(name = "newState") boolean value) {
|
||||
|
||||
if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId);
|
||||
if (roomOpt.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var room = roomOpt.get();
|
||||
room.setLocked(value);
|
||||
return ResponseEntity.ok(true);
|
||||
}
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
package ru.dragonestia.picker.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.dragonestia.picker.controller.response.BucketUserListResponse;
|
||||
import ru.dragonestia.picker.controller.response.LinkUsersWithBucketResponse;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.service.BucketService;
|
||||
import ru.dragonestia.picker.service.NodeService;
|
||||
import ru.dragonestia.picker.service.UserService;
|
||||
import ru.dragonestia.picker.util.NamingValidator;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/nodes/{nodeIdentifier}/buckets/{bucketIdentifier}/users")
|
||||
public class UserBucketController {
|
||||
|
||||
private final NodeService nodeService;
|
||||
private final BucketService bucketService;
|
||||
private final UserService userService;
|
||||
|
||||
@GetMapping
|
||||
ResponseEntity<BucketUserListResponse> usersInsideBucket(@PathVariable(name = "nodeIdentifier") String nodeId,
|
||||
@PathVariable(name = "bucketIdentifier") String bucketId) {
|
||||
|
||||
Bucket bucket;
|
||||
try {
|
||||
var temp = getNodeAndBucket(nodeId, bucketId);
|
||||
bucket = temp.bucket();
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var users = userService.getBucketUsers(bucket);
|
||||
return ResponseEntity.ok(new BucketUserListResponse(bucket.getSlots().getSlots(), users.size(), users));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
ResponseEntity<LinkUsersWithBucketResponse> linkUserWithBucket(@PathVariable(name = "nodeIdentifier") String nodeId,
|
||||
@PathVariable(name = "bucketIdentifier") String bucketId,
|
||||
@RequestParam(name = "users") String userIds,
|
||||
@RequestParam(name = "force") boolean force) {
|
||||
|
||||
Bucket bucket;
|
||||
try {
|
||||
var temp = getNodeAndBucket(nodeId, bucketId);
|
||||
bucket = temp.bucket();
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.status(404).body(new LinkUsersWithBucketResponse(false, error.getMessage()));
|
||||
}
|
||||
|
||||
var list = new LinkedList<User>();
|
||||
for (var username: userIds.split(",")) {
|
||||
if (!NamingValidator.validateUserIdentifier(username)) continue;
|
||||
|
||||
list.add(new User(username));
|
||||
}
|
||||
|
||||
try {
|
||||
userService.linkUsersWithBucket(bucket, list, force);
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.status(400).body(new LinkUsersWithBucketResponse(false, error.getMessage()));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(new LinkUsersWithBucketResponse(true, "Success"));
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
ResponseEntity<?> unlinkUsersForBucket(@PathVariable(name = "nodeIdentifier") String nodeId,
|
||||
@PathVariable(name = "bucketIdentifier") String bucketId,
|
||||
@RequestParam(name = "users") String userIdentifiers) {
|
||||
|
||||
Node node;
|
||||
Bucket bucket;
|
||||
try {
|
||||
var temp = getNodeAndBucket(nodeId, bucketId);
|
||||
node = temp.node();
|
||||
bucket = temp.bucket();
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private record NodeAndBucket(Node node, Bucket bucket) {}
|
||||
|
||||
private NodeAndBucket getNodeAndBucket(String nodeId, String bucketId) {
|
||||
if (!NamingValidator.validateNodeIdentifier(nodeId) || !NamingValidator.validateBucketIdentifier(bucketId)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.findNode(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
var bucketOpt = bucketService.findBucket(Objects.requireNonNull(nodeOpt.get()), bucketId);
|
||||
if (bucketOpt.isEmpty()) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return new NodeAndBucket(nodeOpt.get(), bucketOpt.get());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package ru.dragonestia.picker.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.dragonestia.picker.controller.response.RoomUserListResponse;
|
||||
import ru.dragonestia.picker.controller.response.LinkUsersWithRoomResponse;
|
||||
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;
|
||||
import ru.dragonestia.picker.util.NamingValidator;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/nodes/{nodeId}/rooms/{roomId}/users")
|
||||
public class UserRoomController {
|
||||
|
||||
private final NodeService nodeService;
|
||||
private final RoomService roomService;
|
||||
private final UserService userService;
|
||||
|
||||
@GetMapping
|
||||
ResponseEntity<RoomUserListResponse> usersInsideRoom(@PathVariable(name = "nodeId") String nodeId,
|
||||
@PathVariable(name = "roomId") String bucketId) {
|
||||
|
||||
Room room;
|
||||
try {
|
||||
var temp = getNodeAndRoom(nodeId, bucketId);
|
||||
room = temp.room();
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
var users = userService.getRoomUsers(room);
|
||||
return ResponseEntity.ok(new RoomUserListResponse(room.getSlots().getSlots(), users.size(), users));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
ResponseEntity<LinkUsersWithRoomResponse> linkUserWithRoom(@PathVariable(name = "nodeId") String nodeId,
|
||||
@PathVariable(name = "roomId") String roomId,
|
||||
@RequestParam(name = "userIds") String userIds,
|
||||
@RequestParam(name = "force") boolean force) {
|
||||
|
||||
Room room;
|
||||
try {
|
||||
var temp = getNodeAndRoom(nodeId, roomId);
|
||||
room = temp.room();
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.status(404).body(new LinkUsersWithRoomResponse(false, error.getMessage()));
|
||||
}
|
||||
|
||||
var list = new LinkedList<User>();
|
||||
for (var username: userIds.split(",")) {
|
||||
if (!NamingValidator.validateUserId(username)) continue;
|
||||
|
||||
list.add(new User(username));
|
||||
}
|
||||
|
||||
try {
|
||||
userService.linkUsersWithRoom(room, list, force);
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.status(400).body(new LinkUsersWithRoomResponse(false, error.getMessage()));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(new LinkUsersWithRoomResponse(true, "Success"));
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
ResponseEntity<?> unlinkUsersForBucket(@PathVariable(name = "nodeId") String nodeId,
|
||||
@PathVariable(name = "roomId") String roomId,
|
||||
@RequestParam(name = "userIds") String userIds) {
|
||||
|
||||
Node node;
|
||||
Room room;
|
||||
try {
|
||||
var temp = getNodeAndRoom(nodeId, roomId);
|
||||
node = temp.node();
|
||||
room = temp.room();
|
||||
} catch (Error error) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private record NodeAndRoom(Node node, Room room) {}
|
||||
|
||||
private NodeAndRoom getNodeAndRoom(String nodeId, String roomId) {
|
||||
if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
var nodeOpt = nodeService.find(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId);
|
||||
if (roomOpt.isEmpty()) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return new NodeAndRoom(nodeOpt.get(), roomOpt.get());
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
|
||||
public record BucketInfoResponse(Bucket bucket) {}
|
||||
@ -1,8 +0,0 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record BucketListResponse(String node, List<BucketDTO> buckets) {
|
||||
|
||||
public record BucketDTO(String identifier, int slots, boolean locked) {}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
public record BucketRegisterResponse(boolean success, String message) {}
|
||||
@ -1,3 +0,0 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
public record LinkUsersWithBucketResponse(boolean success, String message) {}
|
||||
@ -0,0 +1,3 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
public record LinkUsersWithRoomResponse(boolean success, String message) {}
|
||||
@ -0,0 +1,5 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
|
||||
public record RoomInfoResponse(Room room) {}
|
||||
@ -0,0 +1,8 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record RoomListResponse(String node, List<RoomDTO> rooms) {
|
||||
|
||||
public record RoomDTO(String id, int slots, boolean locked) {}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
package ru.dragonestia.picker.controller.response;
|
||||
|
||||
public record RoomRegisterResponse(boolean success, String message) {}
|
||||
@ -4,4 +4,4 @@ import ru.dragonestia.picker.model.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record BucketUserListResponse(int slots, int usedSlots, List<User> users) {}
|
||||
public record RoomUserListResponse(int slots, int usedSlots, List<User> users) {}
|
||||
@ -1,13 +1,13 @@
|
||||
package ru.dragonestia.picker.model;
|
||||
|
||||
import lombok.NonNull;
|
||||
import ru.dragonestia.picker.model.type.LoadBalancingMethod;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
|
||||
public record Node(@NonNull String identifier, @NonNull LoadBalancingMethod method) {
|
||||
public record Node(@NonNull String id, @NonNull PickingMode mode) {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return identifier.hashCode();
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -15,7 +15,7 @@ public record Node(@NonNull String identifier, @NonNull LoadBalancingMethod meth
|
||||
if (object == this) return true;
|
||||
if (object == null) return false;
|
||||
if (object instanceof Node other) {
|
||||
return identifier.equals(other.identifier);
|
||||
return id.equals(other.id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -7,16 +7,16 @@ import ru.dragonestia.picker.model.type.SlotLimit;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Bucket {
|
||||
public class Room {
|
||||
|
||||
private final String identifier;
|
||||
private final String nodeIdentifier;
|
||||
private final String id;
|
||||
private final String nodeId;
|
||||
private final SlotLimit slots;
|
||||
private final String payload;
|
||||
private boolean locked = false;
|
||||
|
||||
public static Bucket create(String identifier, Node node, SlotLimit limit, String payload) {
|
||||
return new Bucket(identifier, node.identifier(), limit, payload);
|
||||
public static Room create(String roomId, Node node, SlotLimit limit, String payload) {
|
||||
return new Room(roomId, node.id(), limit, payload);
|
||||
}
|
||||
|
||||
public void setLocked(boolean value) {
|
||||
@ -31,15 +31,15 @@ public class Bucket {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return identifier.hashCode();
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == this) return true;
|
||||
if (object == null) return false;
|
||||
if (object instanceof Bucket other) {
|
||||
return identifier.equals(other.identifier);
|
||||
if (object instanceof Room other) {
|
||||
return id.equals(other.id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -2,11 +2,11 @@ package ru.dragonestia.picker.model;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public record User(@NonNull String identifier) {
|
||||
public record User(@NonNull String id) {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return identifier.hashCode();
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -14,7 +14,7 @@ public record User(@NonNull String identifier) {
|
||||
if (object == this) return true;
|
||||
if (object == null) return false;
|
||||
if (object instanceof User other) {
|
||||
return identifier.equals(other.identifier);
|
||||
return id.equals(other.id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package ru.dragonestia.picker.model.type;
|
||||
|
||||
public enum LoadBalancingMethod {
|
||||
public enum PickingMode {
|
||||
SEQUENTIAL_FILLING,
|
||||
ROUND_ROBIN,
|
||||
LEAST_PICKED,
|
||||
@ -1,32 +0,0 @@
|
||||
package ru.dragonestia.picker.repository;
|
||||
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BucketRepository {
|
||||
|
||||
void createBucket(Bucket bucket);
|
||||
|
||||
void removeBucket(Bucket bucket);
|
||||
|
||||
Optional<Bucket> findBucket(Node node, String identifier);
|
||||
|
||||
List<Bucket> all(Node node);
|
||||
|
||||
default int countAvailableBuckets(Node node) {
|
||||
return countAvailableBuckets(node, 0);
|
||||
}
|
||||
|
||||
int countAvailableBuckets(Node node, int requiredSlots);
|
||||
|
||||
Optional<Bucket> pickFreeBucket(Node node, Collection<User> users);
|
||||
|
||||
void onCreateNode(Node node);
|
||||
|
||||
void onRemoveNode(Node node);
|
||||
}
|
||||
@ -7,11 +7,11 @@ import java.util.Optional;
|
||||
|
||||
public interface NodeRepository {
|
||||
|
||||
void createNode(Node node);
|
||||
void create(Node node);
|
||||
|
||||
void deleteNode(Node node);
|
||||
void delete(Node node);
|
||||
|
||||
Optional<Node> findNode(String nodeId);
|
||||
Optional<Node> find(String nodeId);
|
||||
|
||||
List<Node> all();
|
||||
}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package ru.dragonestia.picker.repository;
|
||||
|
||||
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;
|
||||
|
||||
public interface RoomRepository {
|
||||
|
||||
void create(Room room);
|
||||
|
||||
void remove(Room room);
|
||||
|
||||
Optional<Room> find(Node node, String identifier);
|
||||
|
||||
List<Room> all(Node node);
|
||||
|
||||
default int countAvailable(Node node) {
|
||||
return countAvailable(node, 0);
|
||||
}
|
||||
|
||||
int countAvailable(Node node, int requiredSlots);
|
||||
|
||||
Optional<Room> pickFree(Node node, Collection<User> users);
|
||||
|
||||
void onCreateNode(Node node);
|
||||
|
||||
void onRemoveNode(Node node);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ru.dragonestia.picker.repository;
|
||||
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -9,13 +9,13 @@ import java.util.Map;
|
||||
|
||||
public interface UserRepository {
|
||||
|
||||
Map<User, Boolean> linkWithBucket(Bucket bucket, Collection<User> users, boolean force);
|
||||
Map<User, Boolean> linkWithRoom(Room room, Collection<User> users, boolean force);
|
||||
|
||||
int unlinkWithBucket(Bucket bucket, Collection<User> users);
|
||||
int unlinkWithRoom(Room room, Collection<User> users);
|
||||
|
||||
List<Bucket> findAllLinkedUserBuckets(User user);
|
||||
List<Room> findAllLinkedUserRooms(User user);
|
||||
|
||||
void onRemoveBucket(Bucket bucket);
|
||||
void onRemoveRoom(Room room);
|
||||
|
||||
List<User> usersOf(Bucket bucket);
|
||||
List<User> usersOf(Room room);
|
||||
}
|
||||
|
||||
@ -1,131 +0,0 @@
|
||||
package ru.dragonestia.picker.repository.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.repository.BucketRepository;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class BucketRepositoryImpl implements BucketRepository {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final Map<Node, Buckets> node2bucketsMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void createBucket(Bucket bucket) {
|
||||
var nodeId = bucket.getNodeIdentifier();
|
||||
|
||||
synchronized (node2bucketsMap) {
|
||||
var node = node2bucketsMap.keySet().stream()
|
||||
.filter(n -> bucket.getNodeIdentifier().equals(n.identifier()))
|
||||
.findFirst();
|
||||
|
||||
if (node.isEmpty()) {
|
||||
throw new IllegalArgumentException("Node '" + nodeId + "' does not exist");
|
||||
}
|
||||
|
||||
var buckets = node2bucketsMap.get(node.get());
|
||||
if (buckets.containsKey(bucket.getIdentifier())) {
|
||||
throw new IllegalArgumentException("Bucket already exists");
|
||||
}
|
||||
buckets.put(bucket.getIdentifier(), new BucketContainer(bucket, new AtomicInteger(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBucket(Bucket bucket) {
|
||||
var nodeId = bucket.getNodeIdentifier();
|
||||
var node = node2bucketsMap.keySet().stream()
|
||||
.filter(n -> bucket.getNodeIdentifier().equals(n.identifier()))
|
||||
.findFirst();
|
||||
|
||||
synchronized (node2bucketsMap) {
|
||||
if (node.isEmpty()) {
|
||||
throw new IllegalArgumentException("Node '" + nodeId + "' does not exist");
|
||||
}
|
||||
|
||||
node2bucketsMap.get(node.get()).remove(bucket.getIdentifier());
|
||||
}
|
||||
|
||||
userRepository.onRemoveBucket(bucket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Bucket> findBucket(Node node, String identifier) {
|
||||
synchronized (node2bucketsMap) {
|
||||
if (!node2bucketsMap.containsKey(node)) {
|
||||
throw new IllegalArgumentException("Node '" + node.identifier() + "' does not exist");
|
||||
}
|
||||
|
||||
var result = node2bucketsMap.get(node).getOrDefault(identifier, null);
|
||||
return result == null? Optional.empty() : Optional.of(result.bucket());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Bucket> all(Node node) {
|
||||
synchronized (node2bucketsMap) {
|
||||
return node2bucketsMap.get(node).values().stream().map(BucketContainer::bucket).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAvailableBuckets(Node node, int requiredSlots) {
|
||||
return (int) node2bucketsMap.get(node).values().stream()
|
||||
.filter(bucket -> bucket.isAvailable(requiredSlots))
|
||||
.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Bucket> pickFreeBucket(Node node, Collection<User> users) {
|
||||
synchronized (node2bucketsMap) {
|
||||
if (!node2bucketsMap.containsKey(node)) {
|
||||
throw new IllegalArgumentException("Node '" + node.identifier() + "' does not exist");
|
||||
}
|
||||
|
||||
var requiredSlots = users.size();
|
||||
var container = node2bucketsMap.get(node).values().stream() // TODO: pick bucket with used node balancing method
|
||||
.filter(b -> b.isAvailable(requiredSlots))
|
||||
.findFirst();
|
||||
|
||||
if (container.isPresent()) {
|
||||
var cont = container.get();
|
||||
var addedUsers = userRepository.linkWithBucket(cont.bucket(), users, false);
|
||||
cont.used().getAndAdd((int) addedUsers.values().stream().filter(Boolean.TRUE::equals).count());
|
||||
}
|
||||
|
||||
return container.map(BucketContainer::bucket);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateNode(Node node) {
|
||||
synchronized (node2bucketsMap) {
|
||||
node2bucketsMap.put(node, new Buckets());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveNode(Node node) {
|
||||
synchronized (node2bucketsMap) {
|
||||
node2bucketsMap.remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
private record BucketContainer(Bucket bucket, AtomicInteger used) {
|
||||
|
||||
public boolean isAvailable(int requiredSlots) {
|
||||
return bucket.isAvailable(used.get(), requiredSlots);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Buckets extends LinkedHashMap<String, BucketContainer> {}
|
||||
}
|
||||
@ -3,7 +3,7 @@ package ru.dragonestia.picker.repository.impl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.repository.BucketRepository;
|
||||
import ru.dragonestia.picker.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.repository.NodeRepository;
|
||||
|
||||
import java.util.List;
|
||||
@ -15,33 +15,33 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@RequiredArgsConstructor
|
||||
public class NodeRepositoryImpl implements NodeRepository {
|
||||
|
||||
private final BucketRepository bucketRepository;
|
||||
private final RoomRepository roomRepository;
|
||||
private final Map<String, Node> nodeMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void createNode(Node node) {
|
||||
public void create(Node node) {
|
||||
synchronized (nodeMap) {
|
||||
if (nodeMap.containsKey(node.identifier())) {
|
||||
throw new IllegalArgumentException("Node with id '" + node.identifier() + "' already exists");
|
||||
if (nodeMap.containsKey(node.id())) {
|
||||
throw new IllegalArgumentException("Node with id '" + node.id() + "' already exists");
|
||||
}
|
||||
|
||||
nodeMap.put(node.identifier(), node);
|
||||
nodeMap.put(node.id(), node);
|
||||
}
|
||||
|
||||
bucketRepository.onCreateNode(node);
|
||||
roomRepository.onCreateNode(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteNode(Node node) {
|
||||
public void delete(Node node) {
|
||||
synchronized (nodeMap) {
|
||||
nodeMap.remove(node.identifier());
|
||||
nodeMap.remove(node.id());
|
||||
}
|
||||
|
||||
bucketRepository.onRemoveNode(node);
|
||||
roomRepository.onRemoveNode(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> findNode(String nodeId) {
|
||||
public Optional<Node> find(String nodeId) {
|
||||
synchronized (nodeMap) {
|
||||
return nodeMap.containsKey(nodeId)? Optional.of(nodeMap.get(nodeId)) : Optional.empty();
|
||||
}
|
||||
|
||||
@ -0,0 +1,131 @@
|
||||
package ru.dragonestia.picker.repository.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class RoomRepositoryImpl implements RoomRepository {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final Map<Node, Rooms> node2roomsMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(Room room) {
|
||||
var nodeId = room.getNodeId();
|
||||
|
||||
synchronized (node2roomsMap) {
|
||||
var node = node2roomsMap.keySet().stream()
|
||||
.filter(n -> room.getNodeId().equals(n.id()))
|
||||
.findFirst();
|
||||
|
||||
if (node.isEmpty()) {
|
||||
throw new IllegalArgumentException("Node '" + nodeId + "' does not exist");
|
||||
}
|
||||
|
||||
var rooms = node2roomsMap.get(node.get());
|
||||
if (rooms.containsKey(room.getId())) {
|
||||
throw new IllegalArgumentException("Room already exists");
|
||||
}
|
||||
rooms.put(room.getId(), new RoomContainer(room, new AtomicInteger(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Room room) {
|
||||
var nodeId = room.getNodeId();
|
||||
var node = node2roomsMap.keySet().stream()
|
||||
.filter(n -> room.getNodeId().equals(n.id()))
|
||||
.findFirst();
|
||||
|
||||
synchronized (node2roomsMap) {
|
||||
if (node.isEmpty()) {
|
||||
throw new IllegalArgumentException("Node '" + nodeId + "' does not exist");
|
||||
}
|
||||
|
||||
node2roomsMap.get(node.get()).remove(room.getId());
|
||||
}
|
||||
|
||||
userRepository.onRemoveRoom(room);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Room> find(Node node, String identifier) {
|
||||
synchronized (node2roomsMap) {
|
||||
if (!node2roomsMap.containsKey(node)) {
|
||||
throw new IllegalArgumentException("Node '" + node.id() + "' does not exist");
|
||||
}
|
||||
|
||||
var result = node2roomsMap.get(node).getOrDefault(identifier, null);
|
||||
return result == null? Optional.empty() : Optional.of(result.room());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Room> all(Node node) {
|
||||
synchronized (node2roomsMap) {
|
||||
return node2roomsMap.get(node).values().stream().map(RoomContainer::room).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAvailable(Node node, int requiredSlots) {
|
||||
return (int) node2roomsMap.get(node).values().stream()
|
||||
.filter(bucket -> bucket.isAvailable(requiredSlots))
|
||||
.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Room> pickFree(Node node, Collection<User> users) {
|
||||
synchronized (node2roomsMap) {
|
||||
if (!node2roomsMap.containsKey(node)) {
|
||||
throw new IllegalArgumentException("Node '" + node.id() + "' does not exist");
|
||||
}
|
||||
|
||||
var requiredSlots = users.size();
|
||||
var container = node2roomsMap.get(node).values().stream() // TODO: pick room with used node balancing mode
|
||||
.filter(r -> r.isAvailable(requiredSlots))
|
||||
.findFirst();
|
||||
|
||||
if (container.isPresent()) {
|
||||
var cont = container.get();
|
||||
var addedUsers = userRepository.linkWithRoom(cont.room(), users, false);
|
||||
cont.used().getAndAdd((int) addedUsers.values().stream().filter(Boolean.TRUE::equals).count());
|
||||
}
|
||||
|
||||
return container.map(RoomContainer::room);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateNode(Node node) {
|
||||
synchronized (node2roomsMap) {
|
||||
node2roomsMap.put(node, new Rooms());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveNode(Node node) {
|
||||
synchronized (node2roomsMap) {
|
||||
node2roomsMap.remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
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,7 +1,7 @@
|
||||
package ru.dragonestia.picker.repository.impl;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
|
||||
@ -12,51 +12,51 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
@Repository
|
||||
public class UserRepositoryImpl implements UserRepository {
|
||||
|
||||
private final Map<User, Set<Bucket>> usersMap = new ConcurrentHashMap<>();
|
||||
private final Map<NodeBucketPath, Set<User>> bucketUsers = new ConcurrentHashMap<>();
|
||||
private final Map<User, Set<Room>> usersMap = new ConcurrentHashMap<>();
|
||||
private final Map<NodeRoomPath, Set<User>> roomUsers = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Map<User, Boolean> linkWithBucket(Bucket bucket, Collection<User> users, boolean force) {
|
||||
public Map<User, Boolean> linkWithRoom(Room room, Collection<User> users, boolean force) {
|
||||
var result = new HashMap<User, Boolean>();
|
||||
|
||||
synchronized (usersMap) {
|
||||
var path = new NodeBucketPath(bucket.getNodeIdentifier(), bucket.getIdentifier());
|
||||
var usersSet = bucketUsers.getOrDefault(path, new HashSet<>());
|
||||
var path = new NodeRoomPath(room.getNodeId(), room.getId());
|
||||
var usersSet = roomUsers.getOrDefault(path, new HashSet<>());
|
||||
|
||||
if (force || bucket.getSlots().isUnlimited()) {
|
||||
if (force || room.getSlots().isUnlimited()) {
|
||||
users.forEach(user -> result.put(user, true));
|
||||
} else {
|
||||
for (var user : users) {
|
||||
var set = usersMap.getOrDefault(user, new HashSet<>());
|
||||
result.put(user, !set.contains(bucket));
|
||||
result.put(user, !set.contains(room));
|
||||
}
|
||||
|
||||
if (bucket.getSlots().getSlots() < usersSet.size() + users.size()) {
|
||||
throw new Error("Bucket are full");
|
||||
if (room.getSlots().getSlots() < usersSet.size() + users.size()) {
|
||||
throw new Error("Room are full");
|
||||
}
|
||||
}
|
||||
|
||||
for (var user: users) {
|
||||
var set = usersMap.getOrDefault(user, new HashSet<>());
|
||||
set.add(bucket);
|
||||
set.add(room);
|
||||
usersMap.put(user, set);
|
||||
}
|
||||
|
||||
usersSet.addAll(users);
|
||||
bucketUsers.put(path, usersSet);
|
||||
roomUsers.put(path, usersSet);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int unlinkWithBucket(Bucket bucket, Collection<User> users) {
|
||||
public int unlinkWithRoom(Room room, Collection<User> users) {
|
||||
var counter = new AtomicInteger();
|
||||
synchronized (usersMap) {
|
||||
usersMap.forEach((user, set) -> {
|
||||
if (!set.contains(bucket)) return;
|
||||
if (!set.contains(room)) return;
|
||||
|
||||
set.remove(bucket);
|
||||
set.remove(room);
|
||||
counter.incrementAndGet();
|
||||
|
||||
if (set.isEmpty()) {
|
||||
@ -64,30 +64,30 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
}
|
||||
});
|
||||
|
||||
var path = new NodeBucketPath(bucket.getNodeIdentifier(), bucket.getIdentifier());
|
||||
var set = bucketUsers.getOrDefault(path, new HashSet<>());
|
||||
var path = new NodeRoomPath(room.getNodeId(), room.getId());
|
||||
var set = roomUsers.getOrDefault(path, new HashSet<>());
|
||||
set.removeAll(users);
|
||||
if (set.isEmpty()) {
|
||||
bucketUsers.remove(path);
|
||||
roomUsers.remove(path);
|
||||
} else {
|
||||
bucketUsers.put(path, set);
|
||||
roomUsers.put(path, set);
|
||||
}
|
||||
}
|
||||
return counter.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Bucket> findAllLinkedUserBuckets(User user) {
|
||||
public List<Room> findAllLinkedUserRooms(User user) {
|
||||
synchronized (usersMap) {
|
||||
return usersMap.getOrDefault(user, new HashSet<>()).stream().toList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveBucket(Bucket bucket) {
|
||||
public void onRemoveRoom(Room room) {
|
||||
synchronized (usersMap) {
|
||||
usersMap.forEach((user, set) -> {
|
||||
set.remove(bucket);
|
||||
set.remove(room);
|
||||
if (set.isEmpty()) {
|
||||
usersMap.remove(user);
|
||||
}
|
||||
@ -96,15 +96,15 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> usersOf(Bucket bucket) {
|
||||
public List<User> usersOf(Room room) {
|
||||
synchronized (usersMap) {
|
||||
return bucketUsers.getOrDefault(new NodeBucketPath(bucket.getNodeIdentifier(), bucket.getIdentifier()), new HashSet<>())
|
||||
return roomUsers.getOrDefault(new NodeRoomPath(room.getNodeId(), room.getId()), new HashSet<>())
|
||||
.stream()
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
private record NodeBucketPath(String node, String bucket) {
|
||||
private record NodeRoomPath(String node, String bucket) {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
@ -115,7 +115,7 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
if (o == this) return true;
|
||||
if (o instanceof NodeBucketPath other) {
|
||||
if (o instanceof NodeRoomPath other) {
|
||||
return other.node().equals(node()) && other.bucket().equals(bucket());
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
package ru.dragonestia.picker.service;
|
||||
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BucketService {
|
||||
|
||||
void createBucket(Bucket bucket);
|
||||
|
||||
void removeBucket(Bucket bucket);
|
||||
|
||||
Optional<Bucket> findBucket(Node node, String identifier);
|
||||
|
||||
List<Bucket> allBuckets(Node node);
|
||||
|
||||
default int countAvailableBuckets(Node node) {
|
||||
return countAvailableBuckets(node, 1);
|
||||
}
|
||||
|
||||
int countAvailableBuckets(Node node, int requiredSlots);
|
||||
|
||||
Bucket pickAvailableBucket(Node node, List<User> users);
|
||||
}
|
||||
@ -7,11 +7,11 @@ import java.util.Optional;
|
||||
|
||||
public interface NodeService {
|
||||
|
||||
void createNode(Node node);
|
||||
void create(Node node);
|
||||
|
||||
void removeNode(Node node);
|
||||
void remove(Node node);
|
||||
|
||||
List<Node> allNodes();
|
||||
List<Node> all();
|
||||
|
||||
Optional<Node> findNode(String identifier);
|
||||
Optional<Node> find(String nodeId);
|
||||
}
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
package ru.dragonestia.picker.service;
|
||||
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RoomService {
|
||||
|
||||
void create(Room room);
|
||||
|
||||
void remove(Room room);
|
||||
|
||||
Optional<Room> find(Node node, String roomId);
|
||||
|
||||
List<Room> all(Node node);
|
||||
|
||||
default int countAvailable(Node node) {
|
||||
return countAvailable(node, 1);
|
||||
}
|
||||
|
||||
int countAvailable(Node node, int requiredSlots);
|
||||
|
||||
Room pickAvailable(Node node, List<User> users);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ru.dragonestia.picker.service;
|
||||
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -8,11 +8,11 @@ import java.util.List;
|
||||
|
||||
public interface UserService {
|
||||
|
||||
List<Bucket> getUserBuckets(User user);
|
||||
List<Room> getUserRooms(User user);
|
||||
|
||||
void linkUsersWithBucket(Bucket bucket, Collection<User> users, boolean force);
|
||||
void linkUsersWithRoom(Room room, Collection<User> users, boolean force);
|
||||
|
||||
void unlinkUsersFromBucket(Bucket bucket, Collection<User> users);
|
||||
void unlinkUsersFromRoom(Room room, Collection<User> users);
|
||||
|
||||
List<User> getBucketUsers(Bucket bucket);
|
||||
List<User> getRoomUsers(Room room);
|
||||
}
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
package ru.dragonestia.picker.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.repository.BucketRepository;
|
||||
import ru.dragonestia.picker.service.BucketService;
|
||||
import ru.dragonestia.picker.util.NamingValidator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BucketServiceImpl implements BucketService {
|
||||
|
||||
private final BucketRepository bucketRepository;
|
||||
|
||||
@Override
|
||||
public void createBucket(Bucket bucket) {
|
||||
if (!NamingValidator.validateBucketIdentifier(bucket.getIdentifier())) {
|
||||
throw new Error("Invalid bucket identifier format");
|
||||
}
|
||||
|
||||
bucketRepository.createBucket(bucket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBucket(Bucket bucket) {
|
||||
bucketRepository.removeBucket(bucket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Bucket> findBucket(Node node, String identifier) {
|
||||
return bucketRepository.findBucket(node, identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Bucket> allBuckets(Node node) {
|
||||
return bucketRepository.all(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAvailableBuckets(Node node, int requiredSlots) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bucket pickAvailableBucket(Node node, List<User> users) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
@ -17,26 +17,26 @@ public class NodeServiceImpl implements NodeService {
|
||||
private final NodeRepository nodeRepository;
|
||||
|
||||
@Override
|
||||
public void createNode(Node node) {
|
||||
if (!NamingValidator.validateNodeIdentifier(node.identifier())) {
|
||||
throw new Error("Invalid node identifier format");
|
||||
public void create(Node node) {
|
||||
if (!NamingValidator.validateNodeId(node.id())) {
|
||||
throw new Error("Invalid node id format");
|
||||
}
|
||||
|
||||
nodeRepository.createNode(node);
|
||||
nodeRepository.create(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNode(Node node) {
|
||||
nodeRepository.deleteNode(node);
|
||||
public void remove(Node node) {
|
||||
nodeRepository.delete(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Node> allNodes() {
|
||||
public List<Node> all() {
|
||||
return nodeRepository.all();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> findNode(String identifier) {
|
||||
return nodeRepository.findNode(identifier);
|
||||
public Optional<Node> find(String nodeId) {
|
||||
return nodeRepository.find(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package ru.dragonestia.picker.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.service.RoomService;
|
||||
import ru.dragonestia.picker.util.NamingValidator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RoomServiceImpl implements RoomService {
|
||||
|
||||
private final RoomRepository roomRepository;
|
||||
|
||||
@Override
|
||||
public void create(Room room) {
|
||||
if (!NamingValidator.validateRoomId(room.getId())) {
|
||||
throw new Error("Invalid room id format");
|
||||
}
|
||||
|
||||
roomRepository.create(room);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Room room) {
|
||||
roomRepository.remove(room);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Room> find(Node node, String roomId) {
|
||||
return roomRepository.find(node, roomId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Room> all(Node node) {
|
||||
return roomRepository.all(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAvailable(Node node, int requiredSlots) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Room pickAvailable(Node node, List<User> users) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ package ru.dragonestia.picker.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
import ru.dragonestia.picker.service.UserService;
|
||||
@ -17,22 +17,22 @@ public class UserServiceImpl implements UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public List<Bucket> getUserBuckets(User user) {
|
||||
return userRepository.findAllLinkedUserBuckets(user);
|
||||
public List<Room> getUserRooms(User user) {
|
||||
return userRepository.findAllLinkedUserRooms(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void linkUsersWithBucket(Bucket bucket, Collection<User> users, boolean force) {
|
||||
userRepository.linkWithBucket(bucket, users, force);
|
||||
public void linkUsersWithRoom(Room room, Collection<User> users, boolean force) {
|
||||
userRepository.linkWithRoom(room, users, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlinkUsersFromBucket(Bucket bucket, Collection<User> users) {
|
||||
userRepository.unlinkWithBucket(bucket, users);
|
||||
public void unlinkUsersFromRoom(Room room, Collection<User> users) {
|
||||
userRepository.unlinkWithRoom(room, users);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> getBucketUsers(Bucket bucket) {
|
||||
return userRepository.usersOf(bucket);
|
||||
public List<User> getRoomUsers(Room room) {
|
||||
return userRepository.usersOf(room);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,15 +5,15 @@ import lombok.experimental.UtilityClass;
|
||||
@UtilityClass
|
||||
public class NamingValidator {
|
||||
|
||||
public boolean validateNodeIdentifier(String input) {
|
||||
public boolean validateNodeId(String input) {
|
||||
return input.matches("^[a-z\\d-]+$");
|
||||
}
|
||||
|
||||
public boolean validateBucketIdentifier(String input) {
|
||||
public boolean validateRoomId(String input) {
|
||||
return input.matches("^[a-z\\d-]+$");
|
||||
}
|
||||
|
||||
public boolean validateUserIdentifier(String input) {
|
||||
public boolean validateUserId(String input) {
|
||||
return input.matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
package ru.dragonestia.picker.service;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import ru.dragonestia.picker.model.Bucket;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.type.LoadBalancingMethod;
|
||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class BucketServiceTest {
|
||||
|
||||
private BucketService bucketService;
|
||||
private HashMap<String, Bucket> bucketMap;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
bucketMap = new HashMap<>();
|
||||
bucketService = Mockito.mock(BucketService.class);
|
||||
Mockito.doAnswer(invocation -> {
|
||||
var bucket = invocation.getArgument(0, Bucket.class);
|
||||
|
||||
return null;
|
||||
}).when(bucketService).createBucket(Mockito.any(Bucket.class));
|
||||
}
|
||||
|
||||
Node createNode() {
|
||||
return new Node("test-node", LoadBalancingMethod.ROUND_ROBIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
var node = createNode();
|
||||
|
||||
bucketService.createBucket(Bucket.create("test-bucket", node, SlotLimit.unlimited(), ""));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package ru.dragonestia.picker.service;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class RoomServiceTest {
|
||||
|
||||
private RoomService roomService;
|
||||
private HashMap<String, Room> roomMap;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
roomMap = new HashMap<>();
|
||||
roomService = Mockito.mock(RoomService.class);
|
||||
Mockito.doAnswer(invocation -> {
|
||||
var room = invocation.getArgument(0, Room.class);
|
||||
|
||||
return null;
|
||||
}).when(roomService).create(Mockito.any(Room.class));
|
||||
}
|
||||
|
||||
Node createNode() {
|
||||
return new Node("test-node", PickingMode.ROUND_ROBIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
var node = createNode();
|
||||
|
||||
roomService.create(Room.create("test-room", node, SlotLimit.unlimited(), ""));
|
||||
}
|
||||
}
|
||||
@ -12,21 +12,21 @@ import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import lombok.Getter;
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AddUsers extends Details {
|
||||
|
||||
private final Bucket bucket;
|
||||
private final Room room;
|
||||
private final Checkbox ignoreSlots;
|
||||
private final VerticalLayout usersLayout;
|
||||
|
||||
public AddUsers(Bucket bucket) {
|
||||
public AddUsers(Room room) {
|
||||
super(new H2("Add users"));
|
||||
|
||||
this.bucket = bucket;
|
||||
this.room = room;
|
||||
usersLayout = new VerticalLayout();
|
||||
|
||||
add(addUserToTransacionButton());
|
||||
@ -84,8 +84,8 @@ public class AddUsers extends Details {
|
||||
}
|
||||
|
||||
private TextField createUserIdentifierField(boolean canBeDeleted) {
|
||||
var field = new TextField("User identifier");
|
||||
field.setPlaceholder("example-user-identifier");
|
||||
var field = new TextField("User id");
|
||||
field.setPlaceholder("example-user-id");
|
||||
field.setHelperText("It can be UUID, username, numeric ids, etc");
|
||||
field.setMinWidth(20, Unit.REM);
|
||||
|
||||
|
||||
@ -51,14 +51,14 @@ public class NodeList extends VerticalLayout {
|
||||
var temp = input.trim();
|
||||
|
||||
nodesGrid.setItems(cachedNodes.stream()
|
||||
.filter(node -> node.identifier().startsWith(temp))
|
||||
.filter(node -> node.id().startsWith(temp))
|
||||
.toList());
|
||||
}
|
||||
|
||||
private Grid<Node> createGrid() {
|
||||
var grid = new Grid<>(Node.class, false);
|
||||
grid.addColumn(Node::identifier).setHeader("Identifier");
|
||||
grid.addColumn(node -> node.method().getName()).setHeader("Mode");
|
||||
grid.addColumn(Node::id).setHeader("Identifier");
|
||||
grid.addColumn(node -> node.mode().getName()).setHeader("Mode");
|
||||
grid.addComponentColumn(this::createManageButtons).setHeader("Manage");
|
||||
return grid;
|
||||
}
|
||||
@ -84,12 +84,12 @@ public class NodeList extends VerticalLayout {
|
||||
}
|
||||
|
||||
private void clickDetailsButton(Node node) {
|
||||
getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.identifier()));
|
||||
getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.id()));
|
||||
}
|
||||
|
||||
private void clickRemoveButton(Node node) {
|
||||
var dialog = new Dialog("Confirm node deletion");
|
||||
dialog.add(new Paragraph("Confirm that you want to delete node. Enter '" + node.identifier() + "' to field below and confirm."));
|
||||
dialog.add(new Paragraph("Confirm that you want to delete node. Enter '" + node.id() + "' to field below and confirm."));
|
||||
|
||||
var inputField = new TextField();
|
||||
dialog.add(inputField);
|
||||
@ -98,14 +98,14 @@ public class NodeList extends VerticalLayout {
|
||||
var button = new Button("Confirm");
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||
button.addClickListener(event -> {
|
||||
if (!node.identifier().equals(inputField.getValue())) {
|
||||
if (!node.id().equals(inputField.getValue())) {
|
||||
Notification.show("Invalid input", 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
removeNode(node);
|
||||
Notification.show("Node '" + node.identifier() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
|
||||
Notification.show("Node '" + node.id() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
dialog.close();
|
||||
});
|
||||
@ -129,7 +129,7 @@ public class NodeList extends VerticalLayout {
|
||||
|
||||
private void removeNode(Node node) {
|
||||
if (removeMethod != null) {
|
||||
removeMethod.accept(node.identifier());
|
||||
removeMethod.accept(node.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.model.type.LoadBalancingMethod;
|
||||
import ru.dragonestia.picker.cp.model.type.PickingMode;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@ -25,7 +25,7 @@ public class RegisterNode extends Details {
|
||||
|
||||
private final Function<Node, Response> onSubmit;
|
||||
private final TextField identifierField;
|
||||
private final RadioButtonGroup<LoadBalancingMethod> modeRadio;
|
||||
private final RadioButtonGroup<PickingMode> modeRadio;
|
||||
|
||||
public RegisterNode(Function<Node, Response> onSubmit) {
|
||||
super(new H2("Register node"));
|
||||
@ -58,15 +58,15 @@ public class RegisterNode extends Details {
|
||||
return button;
|
||||
}
|
||||
|
||||
private RadioButtonGroup<LoadBalancingMethod> createModeRadio() {
|
||||
var radio = new RadioButtonGroup<LoadBalancingMethod>("Mode");
|
||||
private RadioButtonGroup<PickingMode> createModeRadio() {
|
||||
var radio = new RadioButtonGroup<PickingMode>("Mode");
|
||||
radio.addThemeVariants(RadioGroupVariant.LUMO_VERTICAL);
|
||||
radio.setRenderer(new ComponentRenderer<Component, LoadBalancingMethod>(mode -> new Span(mode.getName())));
|
||||
radio.setItems(LoadBalancingMethod.SEQUENTIAL_FILLING,
|
||||
LoadBalancingMethod.ROUND_ROBIN,
|
||||
LoadBalancingMethod.LEAST_PICKED);
|
||||
radio.setRenderer(new ComponentRenderer<Component, PickingMode>(mode -> new Span(mode.getName())));
|
||||
radio.setItems(PickingMode.SEQUENTIAL_FILLING,
|
||||
PickingMode.ROUND_ROBIN,
|
||||
PickingMode.LEAST_PICKED);
|
||||
|
||||
radio.setValue(LoadBalancingMethod.SEQUENTIAL_FILLING);
|
||||
radio.setValue(PickingMode.SEQUENTIAL_FILLING);
|
||||
return radio;
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ public class RegisterNode extends Details {
|
||||
|
||||
private @Nullable String validateForm(String identifier) {
|
||||
if (identifier.isEmpty()) {
|
||||
return "Node identifier cannot be empty";
|
||||
return "Node id cannot be empty";
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -88,7 +88,7 @@ public class RegisterNode extends Details {
|
||||
String error = null;
|
||||
if (identifierField.isInvalid() || (error = validateForm(nodeIdentifier)) != null) {
|
||||
if (identifierField.isInvalid()) {
|
||||
error = "Invalid node identifier format";
|
||||
error = "Invalid node id format";
|
||||
}
|
||||
|
||||
Notification.show(error, 3000, Notification.Position.TOP_END)
|
||||
|
||||
@ -13,28 +13,28 @@ import com.vaadin.flow.component.textfield.Autocomplete;
|
||||
import com.vaadin.flow.component.textfield.TextArea;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import org.springframework.lang.Nullable;
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.model.type.SlotLimit;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RegisterBucket extends Details {
|
||||
public class RegisterRoom extends Details {
|
||||
|
||||
private final Node node;
|
||||
private final Function<Bucket, Response> onSubmit;
|
||||
private final Function<Room, Response> onSubmit;
|
||||
private final TextField identifierField;
|
||||
private final TextArea payloadField;
|
||||
private final Checkbox lockedField;
|
||||
|
||||
public RegisterBucket(Node node, Function<Bucket, Response> onSubmit) {
|
||||
super(new H2("Register bucket"));
|
||||
public RegisterRoom(Node node, Function<Room, Response> onSubmit) {
|
||||
super(new H2("Register room"));
|
||||
this.node = node;
|
||||
this.onSubmit = onSubmit;
|
||||
|
||||
var layout = new VerticalLayout();
|
||||
layout.add(createNodeIdentifierField());
|
||||
layout.add(identifierField = createBucketIdentifierField());
|
||||
layout.add(identifierField = createRoomIdentifierField());
|
||||
layout.add(payloadField = createPayloadField());
|
||||
layout.add(lockedField = createLockedField());
|
||||
layout.add(createSubmitButton());
|
||||
@ -45,15 +45,15 @@ public class RegisterBucket extends Details {
|
||||
private TextField createNodeIdentifierField() {
|
||||
var field = new TextField("Node identifier");
|
||||
field.setMinWidth(20, Unit.REM);
|
||||
field.setValue(node.identifier());
|
||||
field.setValue(node.id());
|
||||
field.setReadOnly(true);
|
||||
return field;
|
||||
}
|
||||
|
||||
private TextField createBucketIdentifierField() {
|
||||
private TextField createRoomIdentifierField() {
|
||||
var field = new TextField("Identifier");
|
||||
field.setMinWidth(20, Unit.REM);
|
||||
field.setPlaceholder("example-bucket-id");
|
||||
field.setPlaceholder("example-room-id");
|
||||
field.setHelperText("The field can contain only lowercase letters, numbers and a dash character");
|
||||
field.setRequired(true);
|
||||
field.setPattern("^[a-z\\d-]+$");
|
||||
@ -103,7 +103,7 @@ public class RegisterBucket extends Details {
|
||||
String error = null;
|
||||
if (identifierField.isInvalid() || (error = validateForm(nodeIdentifier)) != null) {
|
||||
if (identifierField.isInvalid()) {
|
||||
error = "Invalid bucket identifier format";
|
||||
error = "Invalid room id format";
|
||||
}
|
||||
|
||||
Notification.show(error, 3000, Notification.Position.TOP_END)
|
||||
@ -111,9 +111,9 @@ public class RegisterBucket extends Details {
|
||||
return;
|
||||
}
|
||||
|
||||
var bucket = Bucket.create(nodeIdentifier, node, SlotLimit.unlimited(), payloadField.getValue());
|
||||
bucket.setLocked(lockedField.getValue());
|
||||
var response = onSubmit.apply(bucket);
|
||||
var room = Room.create(nodeIdentifier, node, SlotLimit.unlimited(), payloadField.getValue());
|
||||
room.setLocked(lockedField.getValue());
|
||||
var response = onSubmit.apply(room);
|
||||
clear();
|
||||
if (response.error()) {
|
||||
Notification.show(response.reason(), 3000, Notification.Position.TOP_END)
|
||||
@ -121,7 +121,7 @@ public class RegisterBucket extends Details {
|
||||
return;
|
||||
}
|
||||
|
||||
Notification.show("Bucket was successfully registered", 3000, Notification.Position.TOP_END)
|
||||
Notification.show("Room was successfully registered", 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
}
|
||||
|
||||
@ -16,32 +16,32 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import lombok.Setter;
|
||||
import ru.dragonestia.picker.cp.model.dto.BucketDTO;
|
||||
import ru.dragonestia.picker.cp.model.dto.RoomDTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BucketList extends VerticalLayout {
|
||||
public class RoomList extends VerticalLayout {
|
||||
|
||||
private final String nodeIdentifier;
|
||||
private final Grid<BucketDTO> bucketsGrid;
|
||||
private final Grid<RoomDTO> roomsGrid;
|
||||
private final TextField searchField;
|
||||
private List<BucketDTO> cachedBuckets;
|
||||
@Setter private Consumer<BucketDTO> removeMethod;
|
||||
private List<RoomDTO> cachedRooms;
|
||||
@Setter private Consumer<RoomDTO> removeMethod;
|
||||
|
||||
public BucketList(String nodeIdentifier, List<BucketDTO> buckets) {
|
||||
public RoomList(String nodeIdentifier, List<RoomDTO> buckets) {
|
||||
this.nodeIdentifier = nodeIdentifier;
|
||||
cachedBuckets = buckets;
|
||||
cachedRooms = buckets;
|
||||
|
||||
add(new H2("Buckets"));
|
||||
add(new H2("Rooms"));
|
||||
add(searchField = createSearchField());
|
||||
add(bucketsGrid = createGrid());
|
||||
add(roomsGrid = createGrid());
|
||||
|
||||
update(buckets);
|
||||
}
|
||||
|
||||
private TextField createSearchField() {
|
||||
var field = new TextField("Search bucket");
|
||||
var field = new TextField("Search room");
|
||||
field.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
|
||||
field.setClearButtonVisible(true);
|
||||
field.setHelperText("Press Enter to search");
|
||||
@ -52,27 +52,27 @@ public class BucketList extends VerticalLayout {
|
||||
private void applySearch(String input) {
|
||||
var temp = input.trim();
|
||||
|
||||
bucketsGrid.setItems(cachedBuckets.stream()
|
||||
.filter(bucket -> bucket.identifier().startsWith(temp))
|
||||
roomsGrid.setItems(cachedRooms.stream()
|
||||
.filter(room -> room.id().startsWith(temp))
|
||||
.toList());
|
||||
}
|
||||
|
||||
private Grid<BucketDTO> createGrid() {
|
||||
var grid = new Grid<>(BucketDTO.class, false);
|
||||
grid.addColumn(BucketDTO::identifier).setHeader("Identifier");
|
||||
grid.addComponentColumn(bucket -> {
|
||||
private Grid<RoomDTO> createGrid() {
|
||||
var grid = new Grid<>(RoomDTO.class, false);
|
||||
grid.addColumn(RoomDTO::id).setHeader("Identifier");
|
||||
grid.addComponentColumn(room -> {
|
||||
var result = new Span();
|
||||
if (bucket.slots() == -1) {
|
||||
if (room.slots() == -1) {
|
||||
result.setText("Unlimited");
|
||||
result.getElement().getThemeList().add("badge contrast");
|
||||
} else {
|
||||
result.setText(Integer.toString(bucket.slots()));
|
||||
result.setText(Integer.toString(room.slots()));
|
||||
}
|
||||
return result;
|
||||
}).setHeader("Slots").setTextAlign(ColumnTextAlign.CENTER);
|
||||
grid.addComponentColumn(bucket -> {
|
||||
grid.addComponentColumn(room -> {
|
||||
var result = new Span();
|
||||
if (bucket.locked()) {
|
||||
if (room.locked()) {
|
||||
result.setText("Yes");
|
||||
result.getElement().getThemeList().add("badge error");
|
||||
} else {
|
||||
@ -84,19 +84,19 @@ public class BucketList extends VerticalLayout {
|
||||
return grid;
|
||||
}
|
||||
|
||||
private HorizontalLayout createManageButtons(BucketDTO bucket) {
|
||||
private HorizontalLayout createManageButtons(RoomDTO room) {
|
||||
var layout = new HorizontalLayout();
|
||||
|
||||
{
|
||||
var button = new Button("Details");
|
||||
button.addClickListener(event -> clickDetailsButton(bucket));
|
||||
button.addClickListener(event -> clickDetailsButton(room));
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
layout.add(button);
|
||||
}
|
||||
|
||||
{
|
||||
var button = new Button("Remove");
|
||||
button.addClickListener(event -> clickRemoveButton(bucket));
|
||||
button.addClickListener(event -> clickRemoveButton(room));
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||
layout.add(button);
|
||||
}
|
||||
@ -104,16 +104,16 @@ public class BucketList extends VerticalLayout {
|
||||
return layout;
|
||||
}
|
||||
|
||||
private void clickDetailsButton(BucketDTO bucket) {
|
||||
private void clickDetailsButton(RoomDTO bucket) {
|
||||
getUI().ifPresent(ui -> {
|
||||
ui.navigate("/nodes/" + nodeIdentifier +
|
||||
"/buckets/" + bucket.identifier());
|
||||
"/rooms/" + bucket.id());
|
||||
});
|
||||
}
|
||||
|
||||
private void clickRemoveButton(BucketDTO bucket) {
|
||||
private void clickRemoveButton(RoomDTO bucket) {
|
||||
var dialog = new Dialog("Confirm bucket deletion");
|
||||
dialog.add(new Paragraph("Confirm that you want to delete bucket. Enter '" + bucket.identifier() + "' to field below and confirm."));
|
||||
dialog.add(new Paragraph("Confirm that you want to delete bucket. Enter '" + bucket.id() + "' to field below and confirm."));
|
||||
|
||||
var inputField = new TextField();
|
||||
dialog.add(inputField);
|
||||
@ -122,14 +122,14 @@ public class BucketList extends VerticalLayout {
|
||||
var button = new Button("Confirm");
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||
button.addClickListener(event -> {
|
||||
if (!bucket.identifier().equals(inputField.getValue())) {
|
||||
if (!bucket.id().equals(inputField.getValue())) {
|
||||
Notification.show("Invalid input", 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
removeBucket(bucket);
|
||||
Notification.show("Bucket '" + bucket.identifier() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
|
||||
Notification.show("Bucket '" + bucket.id() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
dialog.close();
|
||||
});
|
||||
@ -146,12 +146,12 @@ public class BucketList extends VerticalLayout {
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
public void update(List<BucketDTO> buckets) {
|
||||
cachedBuckets = buckets;
|
||||
public void update(List<RoomDTO> buckets) {
|
||||
cachedRooms = buckets;
|
||||
applySearch(searchField.getValue());
|
||||
}
|
||||
|
||||
private void removeBucket(BucketDTO bucket) {
|
||||
private void removeBucket(RoomDTO bucket) {
|
||||
if (removeMethod != null) {
|
||||
removeMethod.accept(bucket);
|
||||
}
|
||||
@ -4,7 +4,7 @@ import com.vaadin.flow.component.grid.ColumnTextAlign;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.User;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -12,14 +12,14 @@ import java.util.List;
|
||||
|
||||
public class UserList extends VerticalLayout {
|
||||
|
||||
private final Bucket bucket;
|
||||
private final Room room;
|
||||
private final Grid<User> usersGrid;
|
||||
private final Span totalUsers = new Span();
|
||||
private final Span occupancy = new Span();
|
||||
private List<User> cachedUsers = new ArrayList<>();
|
||||
|
||||
public UserList(Bucket bucket, List<User> users) {
|
||||
this.bucket = bucket;
|
||||
public UserList(Room room, List<User> users) {
|
||||
this.room = room;
|
||||
|
||||
add(usersGrid = createUsersGrid());
|
||||
|
||||
@ -28,8 +28,8 @@ public class UserList extends VerticalLayout {
|
||||
|
||||
private Grid<User> createUsersGrid() {
|
||||
var grid = new Grid<User>();
|
||||
grid.addColumn(User::identifier).setHeader("User Identifier").setFooter(totalUsers);
|
||||
grid.addColumn(user -> 0).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with buckets") // TODO
|
||||
grid.addColumn(User::id).setHeader("User Identifier").setFooter(totalUsers);
|
||||
grid.addColumn(user -> 0).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms") // TODO
|
||||
.setFooter(occupancy);
|
||||
grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO
|
||||
return grid;
|
||||
@ -39,6 +39,6 @@ public class UserList extends VerticalLayout {
|
||||
cachedUsers = users;
|
||||
usersGrid.setItems(users);
|
||||
totalUsers.setText("Total users: " + users.size());
|
||||
occupancy.setText("Occupancy: %s".formatted(bucket.getUsingPercentage(users.size())));
|
||||
occupancy.setText("Occupancy: %s".formatted(room.getUsingPercentage(users.size())));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
package ru.dragonestia.picker.cp.model;
|
||||
|
||||
import lombok.NonNull;
|
||||
import ru.dragonestia.picker.cp.model.type.LoadBalancingMethod;
|
||||
import ru.dragonestia.picker.cp.model.type.PickingMode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public record Node(@NonNull String identifier, @NonNull LoadBalancingMethod method) implements Serializable {
|
||||
public record Node(@NonNull String id, @NonNull PickingMode mode) implements Serializable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return identifier.hashCode();
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -17,7 +17,7 @@ public record Node(@NonNull String identifier, @NonNull LoadBalancingMethod meth
|
||||
if (object == this) return true;
|
||||
if (object == null) return false;
|
||||
if (object instanceof Node other) {
|
||||
return identifier.equals(other.identifier);
|
||||
return id.equals(other.id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -8,29 +8,30 @@ import ru.dragonestia.picker.cp.model.type.SlotLimit;
|
||||
import java.net.URI;
|
||||
|
||||
@Getter
|
||||
public class Bucket {
|
||||
public class Room {
|
||||
|
||||
private final String identifier;
|
||||
private final String nodeIdentifier;
|
||||
private final String id;
|
||||
private final String nodeId;
|
||||
private final SlotLimit slots;
|
||||
private final String payload;
|
||||
private boolean locked = false;
|
||||
|
||||
@JsonCreator
|
||||
private Bucket(@JsonProperty("identifier") String identifier,
|
||||
@JsonProperty("nodeIdentifier") String nodeIdentifier,
|
||||
private Room(@JsonProperty("id") String id,
|
||||
@JsonProperty("nodeIdentifier") String nodeId,
|
||||
@JsonProperty("slots") SlotLimit slots,
|
||||
@JsonProperty("payload") String payload,
|
||||
@JsonProperty("locked") boolean locked) {
|
||||
this.identifier = identifier;
|
||||
this.nodeIdentifier = nodeIdentifier;
|
||||
|
||||
this.id = id;
|
||||
this.nodeId = nodeId;
|
||||
this.slots = slots;
|
||||
this.payload = payload;
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
public static Bucket create(String identifier, Node node, SlotLimit limit, String payload) {
|
||||
return new Bucket(identifier, node.identifier(), limit, payload, false);
|
||||
public static Room create(String roomId, Node node, SlotLimit limit, String payload) {
|
||||
return new Room(roomId, node.id(), limit, payload, false);
|
||||
}
|
||||
|
||||
public void setLocked(boolean value) {
|
||||
@ -45,21 +46,21 @@ public class Bucket {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return identifier.hashCode();
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == this) return true;
|
||||
if (object == null) return false;
|
||||
if (object instanceof Bucket other) {
|
||||
return identifier.equals(other.identifier);
|
||||
if (object instanceof Room other) {
|
||||
return id.equals(other.id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public URI createApiURI() {
|
||||
return URI.create("/nodes/" + nodeIdentifier + "/buckets/" + identifier);
|
||||
return URI.create("/nodes/" + nodeId + "/rooms/" + id);
|
||||
}
|
||||
|
||||
public String getUsingPercentage(int used) {
|
||||
@ -2,11 +2,11 @@ package ru.dragonestia.picker.cp.model;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public record User(@NonNull String identifier) {
|
||||
public record User(@NonNull String id) {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return identifier.hashCode();
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -14,7 +14,7 @@ public record User(@NonNull String identifier) {
|
||||
if (object == this) return true;
|
||||
if (object == null) return false;
|
||||
if (object instanceof User other) {
|
||||
return identifier.equals(other.identifier);
|
||||
return id.equals(other.id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4,13 +4,13 @@ import ru.dragonestia.picker.cp.model.Node;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public record BucketDTO(String identifier, int slots, boolean locked) {
|
||||
public record RoomDTO(String id, int slots, boolean locked) {
|
||||
|
||||
public URI uriAPI(Node node) {
|
||||
return uriAPI(node.identifier());
|
||||
return uriAPI(node.id());
|
||||
}
|
||||
|
||||
public URI uriAPI(String nodeId) {
|
||||
return URI.create("/nodes/" + nodeId + "/buckets/" + identifier);
|
||||
return URI.create("/nodes/" + nodeId + "/rooms/" + id);
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum LoadBalancingMethod {
|
||||
public enum PickingMode {
|
||||
SEQUENTIAL_FILLING("Sequential filling"),
|
||||
ROUND_ROBIN("Round Robin"),
|
||||
LEAST_PICKED("Least Picked");
|
||||
@ -13,12 +13,12 @@ import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import ru.dragonestia.picker.cp.component.BucketList;
|
||||
import ru.dragonestia.picker.cp.component.RoomList;
|
||||
import ru.dragonestia.picker.cp.component.NavPath;
|
||||
import ru.dragonestia.picker.cp.component.RegisterBucket;
|
||||
import ru.dragonestia.picker.cp.component.RegisterRoom;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.model.dto.BucketDTO;
|
||||
import ru.dragonestia.picker.cp.repository.BucketRepository;
|
||||
import ru.dragonestia.picker.cp.model.dto.RoomDTO;
|
||||
import ru.dragonestia.picker.cp.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.cp.repository.NodeRepository;
|
||||
|
||||
import java.util.List;
|
||||
@ -29,16 +29,16 @@ import java.util.List;
|
||||
public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||
|
||||
private final NodeRepository nodeRepository;
|
||||
private final BucketRepository bucketRepository;
|
||||
private final RoomRepository roomRepository;
|
||||
private Node node;
|
||||
private RegisterBucket registerBucket;
|
||||
private BucketList bucketList;
|
||||
private RegisterRoom registerRoom;
|
||||
private RoomList roomList;
|
||||
|
||||
public NodeDetailsPage(@Autowired NodeRepository nodeRepository,
|
||||
@Autowired BucketRepository bucketRepository) {
|
||||
@Autowired RoomRepository roomRepository) {
|
||||
|
||||
this.nodeRepository = nodeRepository;
|
||||
this.bucketRepository = bucketRepository;
|
||||
this.roomRepository = roomRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -51,7 +51,7 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
||||
var nodeId = nodeIdOpt.get();
|
||||
add(new NavPath(new NavPath.Point("Nodes", "/nodes"), new NavPath.Point(nodeId, "/nodes/" + nodeId)));
|
||||
|
||||
var nodeOpt = nodeRepository.findNode(nodeId);
|
||||
var nodeOpt = nodeRepository.find(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
add(new H2("Error 404"));
|
||||
add(new Paragraph("Node not found"));
|
||||
@ -61,27 +61,27 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
||||
}
|
||||
node = nodeOpt.get();
|
||||
|
||||
initComponents(node, bucketRepository.all(node));
|
||||
initComponents(node, roomRepository.all(node));
|
||||
}
|
||||
|
||||
private void initComponents(Node node, List<BucketDTO> buckets) {
|
||||
private void initComponents(Node node, List<RoomDTO> rooms) {
|
||||
printNodeDetails(node);
|
||||
add(new Hr());
|
||||
add(registerBucket = new RegisterBucket(node, (bucket) -> {
|
||||
add(registerRoom = new RegisterRoom(node, (bucket) -> {
|
||||
try {
|
||||
bucketRepository.register(bucket);
|
||||
return new RegisterBucket.Response(false, null);
|
||||
roomRepository.register(bucket);
|
||||
return new RegisterRoom.Response(false, null);
|
||||
} catch (Error error) {
|
||||
return new RegisterBucket.Response(true, error.getMessage());
|
||||
return new RegisterRoom.Response(true, error.getMessage());
|
||||
} finally {
|
||||
bucketList.update(bucketRepository.all(node));
|
||||
roomList.update(roomRepository.all(node));
|
||||
}
|
||||
}));
|
||||
add(new Hr());
|
||||
add(bucketList = new BucketList(node.identifier(), buckets));
|
||||
bucketList.setRemoveMethod(bucket -> {
|
||||
bucketRepository.remove(node, bucket);
|
||||
bucketList.update(bucketRepository.all(node));
|
||||
add(roomList = new RoomList(node.id(), rooms));
|
||||
roomList.setRemoveMethod(bucket -> {
|
||||
roomRepository.remove(node, bucket);
|
||||
roomList.update(roomRepository.all(node));
|
||||
});
|
||||
}
|
||||
|
||||
@ -89,8 +89,8 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
||||
add(new H2("Node details"));
|
||||
|
||||
var layout = new VerticalLayout();
|
||||
layout.add(new Html("<span>Identifier: <b>" + node.identifier() + "</b></span>"));
|
||||
layout.add(new Html("<span>Mode: <b>" + node.method().getName() + "</b></span>"));
|
||||
layout.add(new Html("<span>Identifier: <b>" + node.id() + "</b></span>"));
|
||||
layout.add(new Html("<span>Mode: <b>" + node.mode().getName() + "</b></span>"));
|
||||
|
||||
add(layout);
|
||||
}
|
||||
|
||||
@ -31,15 +31,15 @@ public class NodesPage extends VerticalLayout {
|
||||
add(new Hr());
|
||||
add(nodeList = createNodeListElement());
|
||||
nodeList.setRemoveMethod(nodeIdentifier -> {
|
||||
nodeRepository.removeNode(nodeIdentifier);
|
||||
nodeList.update(nodeRepository.getNodes());
|
||||
nodeRepository.remove(nodeIdentifier);
|
||||
nodeList.update(nodeRepository.all());
|
||||
});
|
||||
}
|
||||
|
||||
protected RegisterNode createRegisterNodeElement() {
|
||||
return new RegisterNode(node -> {
|
||||
try {
|
||||
nodeRepository.registerNode(node);
|
||||
nodeRepository.register(node);
|
||||
return new RegisterNode.Response(false, "");
|
||||
} catch (Error ex) {
|
||||
return new RegisterNode.Response(true, ex.getMessage());
|
||||
@ -47,12 +47,12 @@ public class NodesPage extends VerticalLayout {
|
||||
log.throwing(ex);
|
||||
return new RegisterNode.Response(true, ex.getMessage());
|
||||
} finally {
|
||||
nodeList.update(nodeRepository.getNodes());
|
||||
nodeList.update(nodeRepository.all());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected NodeList createNodeListElement() {
|
||||
return new NodeList(nodeRepository.getNodes());
|
||||
return new NodeList(nodeRepository.all());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package ru.dragonestia.picker.cp.page;
|
||||
|
||||
import com.vaadin.flow.component.Html;
|
||||
import com.vaadin.flow.component.Text;
|
||||
import com.vaadin.flow.component.Unit;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.html.H2;
|
||||
@ -19,29 +20,29 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import ru.dragonestia.picker.cp.component.AddUsers;
|
||||
import ru.dragonestia.picker.cp.component.NavPath;
|
||||
import ru.dragonestia.picker.cp.component.UserList;
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.repository.BucketRepository;
|
||||
import ru.dragonestia.picker.cp.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.cp.repository.NodeRepository;
|
||||
import ru.dragonestia.picker.cp.repository.UserRepository;
|
||||
|
||||
@Route("/nodes/:nodeId/buckets/:bucketId")
|
||||
public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||
@Route("/nodes/:nodeId/rooms/:roomId")
|
||||
public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||
|
||||
private final NodeRepository nodeRepository;
|
||||
private final BucketRepository bucketRepository;
|
||||
private final RoomRepository roomRepository;
|
||||
private final UserRepository userRepository;
|
||||
private Node node;
|
||||
private Bucket bucket;
|
||||
private Room room;
|
||||
private AddUsers addUsers;
|
||||
private UserList userList;
|
||||
private Button lockBucketButton;
|
||||
private VerticalLayout bucketInfo;
|
||||
private Button lockRoomButton;
|
||||
private VerticalLayout roomInfo;
|
||||
|
||||
@Autowired
|
||||
public BucketDetailsPage(NodeRepository nodeRepository, BucketRepository bucketRepository, UserRepository userRepository) {
|
||||
public RoomDetailsPage(NodeRepository nodeRepository, RoomRepository roomRepository, UserRepository userRepository) {
|
||||
this.nodeRepository = nodeRepository;
|
||||
this.bucketRepository = bucketRepository;
|
||||
this.roomRepository = roomRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@ -53,19 +54,19 @@ public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObse
|
||||
return;
|
||||
}
|
||||
|
||||
var bucketIdOpt = event.getRouteParameters().get("bucketId");
|
||||
if (bucketIdOpt.isEmpty()) {
|
||||
getUI().ifPresent(ui -> ui.navigate("/buckets/" + nodeIdOpt.get()));
|
||||
var roomIdOpt = event.getRouteParameters().get("roomId");
|
||||
if (roomIdOpt.isEmpty()) {
|
||||
getUI().ifPresent(ui -> ui.navigate("/rooms/" + nodeIdOpt.get()));
|
||||
return;
|
||||
}
|
||||
|
||||
var nodeId = nodeIdOpt.get();
|
||||
var bucketId = bucketIdOpt.get();
|
||||
var roomId = roomIdOpt.get();
|
||||
add(new NavPath(new NavPath.Point("Nodes", "/nodes"),
|
||||
new NavPath.Point(nodeId, "/nodes/" + nodeId),
|
||||
new NavPath.Point(bucketId, "/nodes/" + nodeId + "/buckets/" + bucketId)));
|
||||
new NavPath.Point(roomId, "/nodes/" + nodeId + "/rooms/" + roomId)));
|
||||
|
||||
var nodeOpt = nodeRepository.findNode(nodeId);
|
||||
var nodeOpt = nodeRepository.find(nodeId);
|
||||
if (nodeOpt.isEmpty()) {
|
||||
add(new H2("Error 404"));
|
||||
add(new Paragraph("Node not found!"));
|
||||
@ -75,75 +76,75 @@ public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObse
|
||||
}
|
||||
node = nodeOpt.get();
|
||||
|
||||
var bucketOpt = bucketRepository.find(node, bucketId);
|
||||
var bucketOpt = roomRepository.find(node, roomId);
|
||||
if (bucketOpt.isEmpty()) {
|
||||
add(new H2("Error 404"));
|
||||
add(new Paragraph("Bucket not found!"));
|
||||
Notification.show("Bucket '" + nodeId + "' does not exist", 3000, Notification.Position.TOP_END)
|
||||
add(new Paragraph("Room not found!"));
|
||||
Notification.show("Room '" + nodeId + "' does not exist", 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
return;
|
||||
}
|
||||
bucket = bucketOpt.get();
|
||||
room = bucketOpt.get();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
add(new H2("Bucket details"));
|
||||
printBucketDetails();
|
||||
add(new H2("Room details"));
|
||||
printRoomDetails();
|
||||
add(new Hr());
|
||||
add(addUsers = new AddUsers(bucket));
|
||||
add(addUsers = new AddUsers(room));
|
||||
add(new Hr());
|
||||
add(new H2("Users"));
|
||||
add(userList = new UserList(bucket, userRepository.all(bucket)));
|
||||
add(userList = new UserList(room, userRepository.all(room)));
|
||||
}
|
||||
|
||||
private void updateBucketInfo() {
|
||||
bucketInfo.removeAll();
|
||||
bucketInfo.add(new Html("<span>Node identifier: <b>" + bucket.getNodeIdentifier() + "</b></span>"));
|
||||
bucketInfo.add(new Html("<span>Bucket identifier: <b>" + bucket.getIdentifier() + "</b></span>"));
|
||||
bucketInfo.add(new Html("<span>Slots: <b>" + (bucket.getSlots().isUnlimited()? "Unlimited" : bucket.getSlots().slots()) + "</b></span>"));
|
||||
bucketInfo.add(new Html("<span>Locked: <b>" + (bucket.isLocked()? "Yes" : "No") + "</b></span>"));
|
||||
private void updateRoomInfo() {
|
||||
roomInfo.removeAll();
|
||||
roomInfo.add(new Html("<span>Node identifier: <b>" + room.getNodeId() + "</b></span>"));
|
||||
roomInfo.add(new Html("<span>Room identifier: <b>" + room.getId() + "</b></span>"));
|
||||
roomInfo.add(new Html("<span>Slots: <b>" + (room.getSlots().isUnlimited()? "Unlimited" : room.getSlots().slots()) + "</b></span>"));
|
||||
roomInfo.add(new Html("<span>Locked: <b>" + (room.isLocked()? "Yes" : "No") + "</b></span>"));
|
||||
}
|
||||
|
||||
private void printBucketDetails() {
|
||||
add(bucketInfo = new VerticalLayout());
|
||||
bucketInfo.setPadding(false);
|
||||
private void printRoomDetails() {
|
||||
add(roomInfo = new VerticalLayout());
|
||||
roomInfo.setPadding(false);
|
||||
|
||||
updateBucketInfo();
|
||||
add(lockBucketButton = new Button("", event -> changeBucketLockedState()));
|
||||
setLockBucketButtonState();
|
||||
updateRoomInfo();
|
||||
add(lockRoomButton = new Button("", event -> changeBucketLockedState()));
|
||||
setLockRoomButtonState();
|
||||
|
||||
var payload = new TextArea("Payload(" + bucket.getPayload().length() + ")");
|
||||
payload.setValue(bucket.getPayload());
|
||||
var payload = new TextArea("Payload(" + room.getPayload().length() + ")");
|
||||
payload.setValue(room.getPayload());
|
||||
payload.setReadOnly(true);
|
||||
payload.setMinWidth(50, Unit.REM);
|
||||
add(payload);
|
||||
}
|
||||
|
||||
private void setLockBucketButtonState() {
|
||||
if (bucket.isLocked()) {
|
||||
lockBucketButton.setText("Unlock");
|
||||
lockBucketButton.setPrefixComponent(new Icon(VaadinIcon.UNLOCK));
|
||||
private void setLockRoomButtonState() {
|
||||
if (room.isLocked()) {
|
||||
lockRoomButton.setText("Unlock");
|
||||
lockRoomButton.setPrefixComponent(new Icon(VaadinIcon.UNLOCK));
|
||||
} else {
|
||||
lockBucketButton.setText("Lock");
|
||||
lockBucketButton.setPrefixComponent(new Icon(VaadinIcon.LOCK));
|
||||
lockRoomButton.setText("Lock");
|
||||
lockRoomButton.setPrefixComponent(new Icon(VaadinIcon.LOCK));
|
||||
}
|
||||
}
|
||||
|
||||
private void changeBucketLockedState() {
|
||||
var newValue = !bucket.isLocked();
|
||||
var newValue = !room.isLocked();
|
||||
try {
|
||||
bucketRepository.lock(bucket, newValue);
|
||||
roomRepository.lock(room, newValue);
|
||||
} catch (Error error) {
|
||||
Notification.show(error.getMessage(), 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
bucket.setLocked(newValue);
|
||||
setLockBucketButtonState();
|
||||
updateBucketInfo();
|
||||
room.setLocked(newValue);
|
||||
setLockRoomButtonState();
|
||||
updateRoomInfo();
|
||||
|
||||
Notification.show("Success", 3000, Notification.Position.TOP_END)
|
||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||
@ -1,23 +0,0 @@
|
||||
package ru.dragonestia.picker.cp.repository;
|
||||
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.model.dto.BucketDTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BucketRepository {
|
||||
|
||||
List<BucketDTO> all(Node node);
|
||||
|
||||
void register(Bucket bucket);
|
||||
|
||||
void remove(Bucket bucket);
|
||||
|
||||
void remove(Node node, BucketDTO bucket);
|
||||
|
||||
Optional<Bucket> find(Node node, String identifier);
|
||||
|
||||
void lock(Bucket bucket, boolean value);
|
||||
}
|
||||
@ -7,11 +7,11 @@ import java.util.Optional;
|
||||
|
||||
public interface NodeRepository {
|
||||
|
||||
void registerNode(Node node);
|
||||
void register(Node node);
|
||||
|
||||
List<Node> getNodes();
|
||||
List<Node> all();
|
||||
|
||||
Optional<Node> findNode(String identifier);
|
||||
Optional<Node> find(String nodeId);
|
||||
|
||||
void removeNode(String identifier);
|
||||
void remove(String nodeId);
|
||||
}
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package ru.dragonestia.picker.cp.repository;
|
||||
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.model.dto.RoomDTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RoomRepository {
|
||||
|
||||
List<RoomDTO> all(Node node);
|
||||
|
||||
void register(Room room);
|
||||
|
||||
void remove(Room room);
|
||||
|
||||
void remove(Node node, RoomDTO bucket);
|
||||
|
||||
Optional<Room> find(Node node, String roomId);
|
||||
|
||||
void lock(Room room, boolean value);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ru.dragonestia.picker.cp.repository;
|
||||
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.User;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -8,9 +8,9 @@ import java.util.List;
|
||||
|
||||
public interface UserRepository {
|
||||
|
||||
void linkWithBucket(Bucket bucket, Collection<User> users);
|
||||
void linkWithRoom(Room room, Collection<User> users);
|
||||
|
||||
void unlinkFromBucket(Bucket bucket, Collection<User> users);
|
||||
void unlinkFromRoom(Room room, Collection<User> users);
|
||||
|
||||
List<User> all(Bucket bucket);
|
||||
List<User> all(Room room);
|
||||
}
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl;
|
||||
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.model.dto.BucketDTO;
|
||||
import ru.dragonestia.picker.cp.repository.BucketRepository;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.BucketInfoResponse;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.BucketListResponse;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.BucketRegisterResponse;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Log4j2
|
||||
@RequiredArgsConstructor
|
||||
@SpringComponent
|
||||
public class BucketRepositoryImpl implements BucketRepository {
|
||||
|
||||
private final RestUtil rest;
|
||||
|
||||
@Override
|
||||
public List<BucketDTO> all(Node node) {
|
||||
var entity = rest.getEntity(URI.create("/nodes/" + node.identifier() + "/buckets"),
|
||||
BucketListResponse.class);
|
||||
|
||||
if (entity.getStatusCode().value() == 404) {
|
||||
throw new Error("Node with identifier '" + node.identifier() + "' does not exists'");
|
||||
}
|
||||
|
||||
if (!entity.hasBody()) {
|
||||
throw new Error("Bucket list did not present");
|
||||
}
|
||||
|
||||
return Objects.requireNonNull(entity.getBody()).buckets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Bucket bucket) {
|
||||
try {
|
||||
var response = rest.post(URI.create("/nodes/" + bucket.getNodeIdentifier() + "/buckets"),
|
||||
BucketRegisterResponse.class,
|
||||
params -> {
|
||||
params.put("identifier", bucket.getIdentifier());
|
||||
params.put("slots", Integer.toString(bucket.getSlots().slots()));
|
||||
params.put("payload", bucket.getPayload());
|
||||
params.put("locked", Boolean.toString(bucket.isLocked()));
|
||||
});
|
||||
|
||||
if (response.success()) return;
|
||||
throw new Error(response.message());
|
||||
} catch (HttpClientErrorException ex) {
|
||||
var response = ex.getResponseBodyAs(BucketRegisterResponse.class);
|
||||
|
||||
if (response != null) {
|
||||
throw new Error(response.message());
|
||||
}
|
||||
|
||||
log.throwing(ex);
|
||||
throw new Error("Internal error. Check logs");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Bucket bucket) {
|
||||
rest.delete(URI.create("/nodes/" + bucket.getNodeIdentifier() + "/buckets/" + bucket.getIdentifier()), params -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Node node, BucketDTO bucket) {
|
||||
rest.delete(URI.create("/nodes/" + node.identifier() + "/buckets/" + bucket.identifier()), params -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Bucket> find(Node node, String identifier) {
|
||||
try {
|
||||
var response = rest.get(URI.create("/nodes/" + node.identifier() + "/buckets/" + identifier), BucketInfoResponse.class, map -> {});
|
||||
return Optional.of(response.bucket());
|
||||
} catch (Exception ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(Bucket bucket, boolean value) {
|
||||
try {
|
||||
rest.post(URI.create(bucket.createApiURI() + "/lock"), Boolean.class, params -> {
|
||||
params.put("state", Boolean.toString(value));
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
log.throwing(ex);
|
||||
throw new Error("Error when changing bucket locked state");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,14 +21,14 @@ public class NodeRepositoryImpl implements NodeRepository {
|
||||
private final RestUtil rest;
|
||||
|
||||
@Override
|
||||
public void registerNode(Node node) {
|
||||
public void register(Node node) {
|
||||
NodeRegisterResponse response;
|
||||
try {
|
||||
response = rest.post(URI.create("nodes"),
|
||||
NodeRegisterResponse.class,
|
||||
params -> {
|
||||
params.put("identifier", node.identifier());
|
||||
params.put("method", node.method().name());
|
||||
params.put("nodeId", node.id());
|
||||
params.put("method", node.mode().name());
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Internal error", ex);
|
||||
@ -40,14 +40,14 @@ public class NodeRepositoryImpl implements NodeRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Node> getNodes() {
|
||||
public List<Node> all() {
|
||||
return rest.get(URI.create("nodes"), NodeListResponse.class).nodes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> findNode(String identifier) {
|
||||
public Optional<Node> find(String nodeId) {
|
||||
try {
|
||||
var response = rest.get(URI.create("nodes/" + identifier), NodeDetailsResponse.class);
|
||||
var response = rest.get(URI.create("nodes/" + nodeId), NodeDetailsResponse.class);
|
||||
return Optional.of(response.node());
|
||||
} catch (Exception ex) {
|
||||
return Optional.empty();
|
||||
@ -55,7 +55,7 @@ public class NodeRepositoryImpl implements NodeRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNode(String identifier) {
|
||||
rest.delete(URI.create("nodes/" + identifier), params -> {});
|
||||
public void remove(String nodeId) {
|
||||
rest.delete(URI.create("nodes/" + nodeId), params -> {});
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl;
|
||||
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.Node;
|
||||
import ru.dragonestia.picker.cp.model.dto.RoomDTO;
|
||||
import ru.dragonestia.picker.cp.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomInfoResponse;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomListResponse;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomRegisterResponse;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Log4j2
|
||||
@RequiredArgsConstructor
|
||||
@SpringComponent
|
||||
public class RoomRepositoryImpl implements RoomRepository {
|
||||
|
||||
private final RestUtil rest;
|
||||
|
||||
@Override
|
||||
public List<RoomDTO> all(Node node) {
|
||||
var entity = rest.getEntity(URI.create("/nodes/" + node.id() + "/rooms"),
|
||||
RoomListResponse.class);
|
||||
|
||||
if (entity.getStatusCode().value() == 404) {
|
||||
throw new Error("Node with identifier '" + node.id() + "' does not exists'");
|
||||
}
|
||||
|
||||
if (!entity.hasBody()) {
|
||||
throw new Error("Room list did not present");
|
||||
}
|
||||
|
||||
return Objects.requireNonNull(entity.getBody()).rooms();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Room room) {
|
||||
try {
|
||||
var response = rest.post(URI.create("/nodes/" + room.getNodeId() + "/rooms"),
|
||||
RoomRegisterResponse.class,
|
||||
params -> {
|
||||
params.put("roomId", room.getId());
|
||||
params.put("slots", Integer.toString(room.getSlots().slots()));
|
||||
params.put("payload", room.getPayload());
|
||||
params.put("locked", Boolean.toString(room.isLocked()));
|
||||
});
|
||||
|
||||
if (response.success()) return;
|
||||
throw new Error(response.message());
|
||||
} catch (HttpClientErrorException ex) {
|
||||
var response = ex.getResponseBodyAs(RoomRegisterResponse.class);
|
||||
|
||||
if (response != null) {
|
||||
throw new Error(response.message());
|
||||
}
|
||||
|
||||
log.throwing(ex);
|
||||
throw new Error("Internal error. Check logs");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Room room) {
|
||||
rest.delete(URI.create("/nodes/" + room.getNodeId() + "/rooms/" + room.getId()), params -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Node node, RoomDTO room) {
|
||||
rest.delete(URI.create("/nodes/" + node.id() + "/rooms/" + room.id()), params -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Room> find(Node node, String roomId) {
|
||||
try {
|
||||
var response = rest.get(URI.create("/nodes/" + node.id() + "/rooms/" + roomId), RoomInfoResponse.class, map -> {});
|
||||
return Optional.of(response.room());
|
||||
} catch (Exception ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(Room room, boolean value) {
|
||||
try {
|
||||
rest.post(URI.create(room.createApiURI() + "/lock"), Boolean.class, params -> {
|
||||
params.put("newState", Boolean.toString(value));
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
log.throwing(ex);
|
||||
throw new Error("Error when changing room locked state");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,10 +3,10 @@ package ru.dragonestia.picker.cp.repository.impl;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
import ru.dragonestia.picker.cp.model.User;
|
||||
import ru.dragonestia.picker.cp.repository.UserRepository;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.BucketUserListResponse;
|
||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomUserListResponse;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
@ -20,20 +20,20 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
private final RestUtil rest;
|
||||
|
||||
@Override
|
||||
public void linkWithBucket(Bucket bucket, Collection<User> users) {
|
||||
public void linkWithRoom(Room room, Collection<User> users) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlinkFromBucket(Bucket bucket, Collection<User> users) {
|
||||
public void unlinkFromRoom(Room room, Collection<User> users) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> all(Bucket bucket) {
|
||||
public List<User> all(Room room) {
|
||||
try {
|
||||
var response = rest.get(URI.create("/nodes/%s/buckets/%s/users".formatted(bucket.getNodeIdentifier(), bucket.getIdentifier())),
|
||||
BucketUserListResponse.class,
|
||||
var response = rest.get(URI.create("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId())),
|
||||
RoomUserListResponse.class,
|
||||
params -> {});
|
||||
|
||||
return response.users();
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
||||
|
||||
import ru.dragonestia.picker.cp.model.Bucket;
|
||||
|
||||
public record BucketInfoResponse(Bucket bucket) {}
|
||||
@ -1,7 +0,0 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
||||
|
||||
import ru.dragonestia.picker.cp.model.dto.BucketDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record BucketListResponse(String node, List<BucketDTO> buckets) {}
|
||||
@ -1,3 +0,0 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
||||
|
||||
public record BucketRegisterResponse(boolean success, String message) {}
|
||||
@ -0,0 +1,5 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
||||
|
||||
import ru.dragonestia.picker.cp.model.Room;
|
||||
|
||||
public record RoomInfoResponse(Room room) {}
|
||||
@ -0,0 +1,7 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
||||
|
||||
import ru.dragonestia.picker.cp.model.dto.RoomDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record RoomListResponse(String node, List<RoomDTO> rooms) {}
|
||||
@ -0,0 +1,3 @@
|
||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
||||
|
||||
public record RoomRegisterResponse(boolean success, String message) {}
|
||||
@ -4,4 +4,4 @@ import ru.dragonestia.picker.cp.model.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record BucketUserListResponse(int slots, int usedSlots, List<User> users) {}
|
||||
public record RoomUserListResponse(int slots, int usedSlots, List<User> users) {}
|
||||
Loading…
x
Reference in New Issue
Block a user