Added branch endpoints and some minor logic updates.
This commit is contained in:
		
							parent
							
								
									108edac5e7
								
							
						
					
					
						commit
						591f46d60d
					
				| 
						 | 
					@ -11,6 +11,8 @@ import java.util.Optional;
 | 
				
			||||||
@Repository
 | 
					@Repository
 | 
				
			||||||
public interface BranchRepository extends JpaRepository<Branch, Long> {
 | 
					public interface BranchRepository extends JpaRepository<Branch, Long> {
 | 
				
			||||||
	Optional<Branch> findByIdAndRailSystem(long id, RailSystem railSystem);
 | 
						Optional<Branch> findByIdAndRailSystem(long id, RailSystem railSystem);
 | 
				
			||||||
 | 
						Optional<Branch> findByIdAndRailSystemId(long id, long railSystemId);
 | 
				
			||||||
	Optional<Branch> findByNameAndRailSystem(String name, RailSystem railSystem);
 | 
						Optional<Branch> findByNameAndRailSystem(String name, RailSystem railSystem);
 | 
				
			||||||
 | 
						List<Branch> findAllByRailSystemOrderByName(RailSystem railSystem);
 | 
				
			||||||
	List<Branch> findAllByNameAndRailSystem(String name, RailSystem railSystem);
 | 
						List<Branch> findAllByNameAndRailSystem(String name, RailSystem railSystem);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import nl.andrewl.railsignalapi.rest.dto.SignalCreationPayload;
 | 
					import nl.andrewl.railsignalapi.rest.dto.SignalCreationPayload;
 | 
				
			||||||
import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
 | 
					import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
 | 
				
			||||||
import nl.andrewl.railsignalapi.service.SignalService;
 | 
					import nl.andrewl.railsignalapi.service.SignalService;
 | 
				
			||||||
 | 
					import org.springframework.http.ResponseEntity;
 | 
				
			||||||
import org.springframework.web.bind.annotation.*;
 | 
					import org.springframework.web.bind.annotation.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
| 
						 | 
					@ -28,4 +29,10 @@ public class SignalsApiController {
 | 
				
			||||||
	public SignalResponse getSignal(@PathVariable long rsId, @PathVariable long sigId) {
 | 
						public SignalResponse getSignal(@PathVariable long rsId, @PathVariable long sigId) {
 | 
				
			||||||
		return signalService.getSignal(rsId, 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();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,8 @@ public class SignalService {
 | 
				
			||||||
	private final SignalRepository signalRepository;
 | 
						private final SignalRepository signalRepository;
 | 
				
			||||||
	private final BranchRepository branchRepository;
 | 
						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
 | 
						@Transactional
 | 
				
			||||||
	public SignalResponse createSignal(long rsId, SignalCreationPayload payload) {
 | 
						public SignalResponse createSignal(long rsId, SignalCreationPayload payload) {
 | 
				
			||||||
| 
						 | 
					@ -60,17 +61,42 @@ public class SignalService {
 | 
				
			||||||
		return new SignalResponse(signal);
 | 
							return new SignalResponse(signal);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void registerSignalWebSocketSession(long signalId, WebSocketSession session) {
 | 
						@Transactional(readOnly = true)
 | 
				
			||||||
		this.signalWebSocketSessions.put(signalId, session);
 | 
						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) {
 | 
						public void deregisterSignalWebSocketSession(WebSocketSession session) {
 | 
				
			||||||
		this.signalWebSocketSessions.remove(signalId);
 | 
							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
 | 
						@Transactional
 | 
				
			||||||
	public void handleSignalUpdate(long signalId, SignalUpdateMessage updateMessage) {
 | 
						public void handleSignalUpdate(SignalUpdateMessage updateMessage) {
 | 
				
			||||||
		var signal = signalRepository.findById(signalId)
 | 
							var signal = signalRepository.findById(updateMessage.signalId())
 | 
				
			||||||
				.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
									.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
				
			||||||
		Branch fromBranch = null;
 | 
							Branch fromBranch = null;
 | 
				
			||||||
		Branch toBranch = null;
 | 
							Branch toBranch = null;
 | 
				
			||||||
| 
						 | 
					@ -102,15 +128,21 @@ public class SignalService {
 | 
				
			||||||
				branchRepository.save(fromBranch);
 | 
									branchRepository.save(fromBranch);
 | 
				
			||||||
				broadcastToConnectedSignals(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) {
 | 
						private void broadcastToConnectedSignals(Branch branch) {
 | 
				
			||||||
		ObjectMapper mapper = new ObjectMapper();
 | 
					 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			WebSocketMessage<String> msg = new TextMessage(mapper.writeValueAsString(new BranchUpdateMessage(branch.getId(), branch.getStatus().name())));
 | 
								WebSocketMessage<String> msg = new TextMessage(mapper.writeValueAsString(new BranchUpdateMessage(branch.getId(), branch.getStatus().name())));
 | 
				
			||||||
			signalRepository.findAllConnectedToBranch(branch).stream()
 | 
								signalRepository.findAllConnectedToBranch(branch).stream()
 | 
				
			||||||
					.map(s -> signalWebSocketSessions.get(s.getId()))
 | 
										.map(s -> getSignalWebSocketSession(s.getId()))
 | 
				
			||||||
					.filter(Objects::nonNull)
 | 
										.filter(Objects::nonNull)
 | 
				
			||||||
					.forEach(session -> {
 | 
										.forEach(session -> {
 | 
				
			||||||
						try {
 | 
											try {
 | 
				
			||||||
| 
						 | 
					@ -140,4 +172,11 @@ public class SignalService {
 | 
				
			||||||
				.map(SignalResponse::new)
 | 
									.map(SignalResponse::new)
 | 
				
			||||||
				.toList();
 | 
									.toList();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Transactional
 | 
				
			||||||
 | 
						public void deleteSignal(long rsId, long sigId) {
 | 
				
			||||||
 | 
							var s = signalRepository.findByIdAndRailSystemId(sigId, rsId)
 | 
				
			||||||
 | 
									.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
				
			||||||
 | 
							signalRepository.delete(s);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.websocket;
 | 
					package nl.andrewl.railsignalapi.websocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public record SignalUpdateMessage(
 | 
					public record SignalUpdateMessage(
 | 
				
			||||||
 | 
							long signalId,
 | 
				
			||||||
		long fromBranchId,
 | 
							long fromBranchId,
 | 
				
			||||||
		long toBranchId,
 | 
							long toBranchId,
 | 
				
			||||||
		String type
 | 
							String type
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,9 @@ import org.springframework.web.socket.TextMessage;
 | 
				
			||||||
import org.springframework.web.socket.WebSocketSession;
 | 
					import org.springframework.web.socket.WebSocketSession;
 | 
				
			||||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
 | 
					import org.springframework.web.socket.handler.TextWebSocketHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashSet;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
@RequiredArgsConstructor
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
| 
						 | 
					@ -24,27 +27,23 @@ public class SignalWebSocketHandler extends TextWebSocketHandler {
 | 
				
			||||||
			session.close(CloseStatus.PROTOCOL_ERROR);
 | 
								session.close(CloseStatus.PROTOCOL_ERROR);
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		long signalId = Long.parseLong(signalIdHeader);
 | 
							Set<Long> ids = new HashSet<>();
 | 
				
			||||||
		session.getAttributes().put("signalId", signalId);
 | 
							for (var idStr : signalIdHeader.split(",")) {
 | 
				
			||||||
		signalService.registerSignalWebSocketSession(signalId, session);
 | 
								ids.add(Long.parseLong(idStr.trim()));
 | 
				
			||||||
		log.info("Connection established with signal {}.", signalId);
 | 
							}
 | 
				
			||||||
 | 
							signalService.registerSignalWebSocketSession(ids, session);
 | 
				
			||||||
 | 
							log.info("Connection established with signals {}.", ids);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
 | 
						protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
 | 
				
			||||||
		var msg = mapper.readValue(message.getPayload(), SignalUpdateMessage.class);
 | 
							var msg = mapper.readValue(message.getPayload(), SignalUpdateMessage.class);
 | 
				
			||||||
		Long signalId = (Long) session.getAttributes().get("signalId");
 | 
							signalService.handleSignalUpdate(msg);
 | 
				
			||||||
		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);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
 | 
						public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
 | 
				
			||||||
		signalService.deregisterSignalWebSocketSession((Long) session.getAttributes().get("signalId"));
 | 
							signalService.deregisterSignalWebSocketSession(session);
 | 
				
			||||||
		log.info("Closed connection to signal {}. Status: {}", session.getAttributes().get("signalId"), status.toString());
 | 
							log.info("Closed connection {}. Status: {}", session.getId(), status.toString());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue