Added branch endpoints and some minor logic updates.

This commit is contained in:
Andrew Lalis 2021-11-22 22:41:02 +01:00
parent 108edac5e7
commit 591f46d60d
7 changed files with 136 additions and 23 deletions

View File

@ -11,6 +11,8 @@ import java.util.Optional;
@Repository
public interface BranchRepository extends JpaRepository<Branch, Long> {
Optional<Branch> findByIdAndRailSystem(long id, RailSystem railSystem);
Optional<Branch> findByIdAndRailSystemId(long id, long railSystemId);
Optional<Branch> findByNameAndRailSystem(String name, RailSystem railSystem);
List<Branch> findAllByRailSystemOrderByName(RailSystem railSystem);
List<Branch> findAllByNameAndRailSystem(String name, RailSystem railSystem);
}

View File

@ -0,0 +1,27 @@
package nl.andrewl.railsignalapi.rest;
import lombok.RequiredArgsConstructor;
import nl.andrewl.railsignalapi.rest.dto.BranchResponse;
import nl.andrewl.railsignalapi.service.BranchService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(path = "/api/railSystems/{rsId}/branches")
@RequiredArgsConstructor
public class BranchesController {
private final BranchService branchService;
@GetMapping
public List<BranchResponse> getAllBranches(@PathVariable long rsId) {
return branchService.getAllBranches(rsId);
}
@DeleteMapping(path = "/{branchId}")
public ResponseEntity<?> deleteBranch(@PathVariable long rsId, @PathVariable long branchId) {
branchService.deleteBranch(rsId, branchId);
return ResponseEntity.noContent().build();
}
}

View File

@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import nl.andrewl.railsignalapi.rest.dto.SignalCreationPayload;
import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
import nl.andrewl.railsignalapi.service.SignalService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -28,4 +29,10 @@ public class SignalsApiController {
public SignalResponse getSignal(@PathVariable long rsId, @PathVariable long sigId) {
return signalService.getSignal(rsId, sigId);
}
@DeleteMapping(path = "/{sigId}")
public ResponseEntity<?> deleteSignal(@PathVariable long rsId, @PathVariable long sigId) {
signalService.deleteSignal(rsId, sigId);
return ResponseEntity.noContent().build();
}
}

View File

@ -0,0 +1,38 @@
package nl.andrewl.railsignalapi.service;
import lombok.RequiredArgsConstructor;
import nl.andrewl.railsignalapi.dao.BranchRepository;
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
import nl.andrewl.railsignalapi.rest.dto.BranchResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Service
@RequiredArgsConstructor
public class BranchService {
private final BranchRepository branchRepository;
private final RailSystemRepository railSystemRepository;
@Transactional
public void deleteBranch(long rsId, long branchId) {
var branch = branchRepository.findByIdAndRailSystemId(branchId, rsId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (!branch.getSignalConnections().isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Branch should not be connected to any signals.");
}
branchRepository.delete(branch);
}
@Transactional(readOnly = true)
public List<BranchResponse> getAllBranches(long rsId) {
var rs = railSystemRepository.findById(rsId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return branchRepository.findAllByRailSystemOrderByName(rs).stream()
.map(BranchResponse::new)
.toList();
}
}

View File

@ -33,7 +33,8 @@ public class SignalService {
private final SignalRepository signalRepository;
private final BranchRepository branchRepository;
private final Map<Long, WebSocketSession> signalWebSocketSessions = new ConcurrentHashMap<>();
private final ObjectMapper mapper = new ObjectMapper();
private final Map<WebSocketSession, Set<Long>> signalWebSocketSessions = new ConcurrentHashMap<>();
@Transactional
public SignalResponse createSignal(long rsId, SignalCreationPayload payload) {
@ -60,17 +61,42 @@ public class SignalService {
return new SignalResponse(signal);
}
public void registerSignalWebSocketSession(long signalId, WebSocketSession session) {
this.signalWebSocketSessions.put(signalId, session);
@Transactional(readOnly = true)
public void registerSignalWebSocketSession(Set<Long> signalIds, WebSocketSession session) {
this.signalWebSocketSessions.put(session, signalIds);
// Instantly send a data packet so that the signals are up-to-date.
for (var signalId : signalIds) {
var signal = signalRepository.findById(signalId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid signal id."));
for (var branchConnection : signal.getBranchConnections()) {
try {
session.sendMessage(new TextMessage(mapper.writeValueAsString(
new BranchUpdateMessage(
branchConnection.getBranch().getId(),
branchConnection.getBranch().getStatus().name()
)
)));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void deregisterSignalWebSocketSession(long signalId) {
this.signalWebSocketSessions.remove(signalId);
public void deregisterSignalWebSocketSession(WebSocketSession session) {
this.signalWebSocketSessions.remove(session);
}
public WebSocketSession getSignalWebSocketSession(long signalId) {
for (var entry : signalWebSocketSessions.entrySet()) {
if (entry.getValue().contains(signalId)) return entry.getKey();
}
return null;
}
@Transactional
public void handleSignalUpdate(long signalId, SignalUpdateMessage updateMessage) {
var signal = signalRepository.findById(signalId)
public void handleSignalUpdate(SignalUpdateMessage updateMessage) {
var signal = signalRepository.findById(updateMessage.signalId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
Branch fromBranch = null;
Branch toBranch = null;
@ -102,15 +128,21 @@ public class SignalService {
branchRepository.save(fromBranch);
broadcastToConnectedSignals(fromBranch);
}
} else if (updateType == SignalUpdateType.BEGIN) {
if (fromBranch.getStatus() != BranchStatus.OCCUPIED) {
log.info("Updating branch {} status from {} to {}.", fromBranch.getName(), fromBranch.getStatus(), BranchStatus.OCCUPIED);
fromBranch.setStatus(BranchStatus.OCCUPIED);
branchRepository.save(fromBranch);
broadcastToConnectedSignals(fromBranch);
}
}
}
private void broadcastToConnectedSignals(Branch branch) {
ObjectMapper mapper = new ObjectMapper();
try {
WebSocketMessage<String> msg = new TextMessage(mapper.writeValueAsString(new BranchUpdateMessage(branch.getId(), branch.getStatus().name())));
signalRepository.findAllConnectedToBranch(branch).stream()
.map(s -> signalWebSocketSessions.get(s.getId()))
.map(s -> getSignalWebSocketSession(s.getId()))
.filter(Objects::nonNull)
.forEach(session -> {
try {
@ -140,4 +172,11 @@ public class SignalService {
.map(SignalResponse::new)
.toList();
}
@Transactional
public void deleteSignal(long rsId, long sigId) {
var s = signalRepository.findByIdAndRailSystemId(sigId, rsId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
signalRepository.delete(s);
}
}

View File

@ -1,6 +1,7 @@
package nl.andrewl.railsignalapi.websocket;
public record SignalUpdateMessage(
long signalId,
long fromBranchId,
long toBranchId,
String type

View File

@ -10,6 +10,9 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.HashSet;
import java.util.Set;
@Component
@RequiredArgsConstructor
@Slf4j
@ -24,27 +27,23 @@ public class SignalWebSocketHandler extends TextWebSocketHandler {
session.close(CloseStatus.PROTOCOL_ERROR);
return;
}
long signalId = Long.parseLong(signalIdHeader);
session.getAttributes().put("signalId", signalId);
signalService.registerSignalWebSocketSession(signalId, session);
log.info("Connection established with signal {}.", signalId);
Set<Long> ids = new HashSet<>();
for (var idStr : signalIdHeader.split(",")) {
ids.add(Long.parseLong(idStr.trim()));
}
signalService.registerSignalWebSocketSession(ids, session);
log.info("Connection established with signals {}.", ids);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
var msg = mapper.readValue(message.getPayload(), SignalUpdateMessage.class);
Long signalId = (Long) session.getAttributes().get("signalId");
if (signalId == null) {
log.warn("Got text message from a websocket session that did not establish a signalId session attribute.");
} else {
log.info("Received update from signal {}.", signalId);
signalService.handleSignalUpdate(signalId, msg);
}
signalService.handleSignalUpdate(msg);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
signalService.deregisterSignalWebSocketSession((Long) session.getAttributes().get("signalId"));
log.info("Closed connection to signal {}. Status: {}", session.getAttributes().get("signalId"), status.toString());
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
signalService.deregisterSignalWebSocketSession(session);
log.info("Closed connection {}. Status: {}", session.getId(), status.toString());
}
}