Updated component creation and update workflow.
This commit is contained in:
parent
6cfc630310
commit
2cc3c2259a
|
@ -17,6 +17,7 @@ public interface ComponentRepository<T extends Component> extends JpaRepository<
|
|||
Optional<T> findByIdAndRailSystemId(long id, long rsId);
|
||||
|
||||
boolean existsByNameAndRailSystem(String name, RailSystem rs);
|
||||
boolean existsByNameAndRailSystemId(String name, long rsId);
|
||||
|
||||
@Query("SELECT c FROM Component c " +
|
||||
"WHERE c.railSystem = :rs AND " +
|
||||
|
|
|
@ -39,6 +39,7 @@ public abstract class Component {
|
|||
* components in the rail system.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Setter
|
||||
private String name;
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,12 +3,11 @@ package nl.andrewl.railsignalapi.model.component;
|
|||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import nl.andrewl.railsignalapi.model.RailSystem;
|
||||
import nl.andrewl.railsignalapi.model.component.Component;
|
||||
import nl.andrewl.railsignalapi.model.component.ComponentType;
|
||||
import nl.andrewl.railsignalapi.model.component.Position;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
|
||||
/**
|
||||
* A simple label element that allows text to be placed in the rail system
|
||||
|
@ -19,6 +18,7 @@ import javax.persistence.*;
|
|||
@Getter
|
||||
public class Label extends Component {
|
||||
@Column(nullable = false)
|
||||
@Setter
|
||||
private String text;
|
||||
|
||||
public Label(RailSystem rs, Position position, String name, String text) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewl.railsignalapi.model.component;
|
|||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import nl.andrewl.railsignalapi.model.RailSystem;
|
||||
import nl.andrewl.railsignalapi.model.Segment;
|
||||
|
||||
|
@ -23,6 +24,7 @@ public class Signal extends Component {
|
|||
* The segment that this signal connects to.
|
||||
*/
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@Setter
|
||||
private Segment segment;
|
||||
|
||||
public Signal(RailSystem railSystem, Position position, String name, Segment segment) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package nl.andrewl.railsignalapi.rest;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.in.ComponentPayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.SimpleComponentResponse;
|
||||
|
@ -13,6 +12,7 @@ import org.springframework.data.web.PageableDefault;
|
|||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
|
@ -43,7 +43,7 @@ public class ComponentsApiController {
|
|||
}
|
||||
|
||||
@PostMapping
|
||||
public ComponentResponse createComponent(@PathVariable long rsId, @RequestBody ComponentPayload payload) {
|
||||
public ComponentResponse createComponent(@PathVariable long rsId, @RequestBody @Valid ComponentPayload payload) {
|
||||
return componentCreationService.create(rsId, payload);
|
||||
}
|
||||
|
||||
|
@ -53,8 +53,8 @@ public class ComponentsApiController {
|
|||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PatchMapping(path = "/{cId}/connectedNodes")
|
||||
public ComponentResponse updateConnectedNodes(@PathVariable long rsId, @PathVariable long cId, @RequestBody PathNodeUpdatePayload payload) {
|
||||
return componentService.updatePath(rsId, cId, payload);
|
||||
@PatchMapping(path = "/{cId}")
|
||||
public ComponentResponse updateComponent(@PathVariable long rsId, @PathVariable long cId, @RequestBody @Valid ComponentPayload payload) {
|
||||
return componentService.updateComponent(rsId, cId, payload);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PathNodeUpdatePayload {
|
||||
public List<NodeIdObj> connectedNodes;
|
||||
public static class NodeIdObj {
|
||||
public long id;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,21 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component.in;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
public class SegmentBoundaryPayload extends ComponentPayload {
|
||||
@NotEmpty @Size(min = 1, max = 2)
|
||||
public SegmentPayload[] segments;
|
||||
|
||||
@NotNull @Size(max = 2)
|
||||
public NodePayload[] connectedNodes;
|
||||
|
||||
public static class SegmentPayload {
|
||||
public long id;
|
||||
}
|
||||
|
||||
public static class NodePayload {
|
||||
public long id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
|
@ -59,7 +60,7 @@ public class ComponentCreationService {
|
|||
|
||||
private Component createSignal(RailSystem rs, SignalPayload payload) {
|
||||
long segmentId = payload.segment.id;
|
||||
Segment segment = segmentRepository.findById(segmentId)
|
||||
Segment segment = segmentRepository.findByIdAndRailSystemId(segmentId, rs.getId())
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
return new Signal(rs, payload.position, payload.name, segment);
|
||||
}
|
||||
|
@ -70,7 +71,7 @@ public class ComponentCreationService {
|
|||
for (var config : payload.possibleConfigurations) {
|
||||
Set<PathNode> pathNodes = new HashSet<>();
|
||||
for (var node : config.nodes) {
|
||||
Component c = componentRepository.findById(node.id)
|
||||
Component c = componentRepository.findByIdAndRailSystemId(node.id, rs.getId())
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
if (c instanceof PathNode pathNode) {
|
||||
pathNodes.add(pathNode);
|
||||
|
@ -78,9 +79,12 @@ public class ComponentCreationService {
|
|||
pathNode.getConnectedNodes().add(s);
|
||||
componentRepository.save(pathNode);
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id " + node.id + " does not refer to a PathNode component.");
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id " + node.id + " does not refer to a valid path node.");
|
||||
}
|
||||
}
|
||||
if (pathNodes.size() != 2) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid switch configuration. All switch configurations must connect 2 path nodes.");
|
||||
}
|
||||
s.getPossibleConfigurations().add(new SwitchConfiguration(s, pathNodes));
|
||||
}
|
||||
if (s.getPossibleConfigurations().size() < 2) {
|
||||
|
@ -92,13 +96,33 @@ public class ComponentCreationService {
|
|||
private Component createSegmentBoundary(RailSystem rs, SegmentBoundaryPayload payload) {
|
||||
Set<Segment> segments = new HashSet<>();
|
||||
for (var segmentP : payload.segments) {
|
||||
Segment segment = segmentRepository.findById(segmentP.id)
|
||||
Segment segment = segmentRepository.findByIdAndRailSystemId(segmentP.id, rs.getId())
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
segments.add(segment);
|
||||
}
|
||||
if (segments.size() < 1 || segments.size() > 2) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid number of segments.");
|
||||
}
|
||||
return new SegmentBoundaryNode(rs, payload.position, payload.name, new HashSet<>(), segments);
|
||||
|
||||
Set<PathNode> connectedNodes = new HashSet<>();
|
||||
if (payload.connectedNodes != null) {
|
||||
for (var nodeData : payload.connectedNodes) {
|
||||
var component = componentRepository.findByIdAndRailSystemId(nodeData.id, rs.getId())
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path node: " + nodeData.id));
|
||||
if (component instanceof PathNode pathNode) {
|
||||
connectedNodes.add(pathNode);
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path node: " + nodeData.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var segmentBoundary = new SegmentBoundaryNode(rs, payload.position, payload.name, connectedNodes, segments);
|
||||
segmentBoundary = componentRepository.save(segmentBoundary);
|
||||
for (var connectedNode : connectedNodes) {
|
||||
connectedNode.getConnectedNodes().add(segmentBoundary);
|
||||
componentRepository.save(connectedNode);
|
||||
}
|
||||
return segmentBoundary;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ package nl.andrewl.railsignalapi.service;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import nl.andrewl.railsignalapi.dao.ComponentRepository;
|
||||
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
|
||||
import nl.andrewl.railsignalapi.dao.SegmentRepository;
|
||||
import nl.andrewl.railsignalapi.dao.SwitchConfigurationRepository;
|
||||
import nl.andrewl.railsignalapi.model.component.Component;
|
||||
import nl.andrewl.railsignalapi.model.component.PathNode;
|
||||
import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
|
||||
import nl.andrewl.railsignalapi.model.Segment;
|
||||
import nl.andrewl.railsignalapi.model.component.*;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.in.*;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.SimpleComponentResponse;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
@ -28,6 +29,7 @@ public class ComponentService {
|
|||
private final ComponentRepository<Component> componentRepository;
|
||||
private final RailSystemRepository railSystemRepository;
|
||||
private final SwitchConfigurationRepository switchConfigurationRepository;
|
||||
private final SegmentRepository segmentRepository;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<ComponentResponse> getComponents(long rsId) {
|
||||
|
@ -72,38 +74,113 @@ public class ComponentService {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public ComponentResponse updatePath(long rsId, long cId, PathNodeUpdatePayload payload) {
|
||||
var c = componentRepository.findByIdAndRailSystemId(cId, rsId)
|
||||
public ComponentResponse updateComponent(long rsId, long componentId, ComponentPayload payload) {
|
||||
var c = componentRepository.findByIdAndRailSystemId(componentId, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
if (!(c instanceof PathNode p)) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component is not a PathNode.");
|
||||
Set<PathNode> newNodes = new HashSet<>();
|
||||
for (var nodeObj : payload.connectedNodes) {
|
||||
long id = nodeObj.id;
|
||||
var c1 = componentRepository.findByIdAndRailSystemId(id, rsId);
|
||||
if (c1.isPresent() && c1.get() instanceof PathNode pn) {
|
||||
newNodes.add(pn);
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with id " + id + " is not a PathNode in the same rail system.");
|
||||
if (!c.getType().name().equalsIgnoreCase(payload.type)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot update a component's type. Remove and create a new component instead.");
|
||||
}
|
||||
if (!c.getName().equals(payload.name) && componentRepository.existsByNameAndRailSystemId(payload.name, rsId)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot rename component because another component has that name.");
|
||||
}
|
||||
c.setName(payload.name);
|
||||
c.getPosition().setX(payload.position.getX());
|
||||
c.getPosition().setY(payload.position.getY());
|
||||
c.getPosition().setZ(payload.position.getZ());
|
||||
if (c instanceof Signal s && payload instanceof SignalPayload sp) {
|
||||
updateSignal(s, sp, rsId);
|
||||
}
|
||||
if (c instanceof SegmentBoundaryNode sb && payload instanceof SegmentBoundaryPayload sbp) {
|
||||
updateSegmentBoundary(sb, sbp, rsId);
|
||||
}
|
||||
if (c instanceof Label lbl && payload instanceof LabelPayload lp) {
|
||||
lbl.setText(lp.text);
|
||||
}
|
||||
if (c instanceof Switch sw && payload instanceof SwitchPayload sp) {
|
||||
updateSwitch(sw, sp, rsId);
|
||||
}
|
||||
return ComponentResponse.of(componentRepository.save(c));
|
||||
}
|
||||
|
||||
private void updateSignal(Signal s, SignalPayload sp, long rsId) {
|
||||
Segment segment = segmentRepository.findByIdAndRailSystemId(sp.segment.id, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signal's attached segment with id " + sp.segment.id + " is invalid."));
|
||||
s.setSegment(segment);
|
||||
}
|
||||
|
||||
private void updateSegmentBoundary(SegmentBoundaryNode sb, SegmentBoundaryPayload sbp, long rsId) {
|
||||
Set<Segment> newSegments = new HashSet<>();
|
||||
for (var segData : sbp.segments) {
|
||||
newSegments.add(segmentRepository.findByIdAndRailSystemId(segData.id, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Segment boundary's attached segment with id " + segData.id + " is invalid.")));
|
||||
}
|
||||
sb.getSegments().retainAll(newSegments);
|
||||
sb.getSegments().addAll(newSegments);
|
||||
|
||||
if (sbp.connectedNodes != null) {
|
||||
Set<PathNode> connectedNodes = new HashSet<>();
|
||||
for (var nodeData : sbp.connectedNodes) {
|
||||
var component = componentRepository.findByIdAndRailSystemId(nodeData.id, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path node: " + nodeData.id));
|
||||
if (component instanceof PathNode pathNode && !component.equals(sb)) {
|
||||
connectedNodes.add(pathNode);
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path node: " + nodeData.id);
|
||||
}
|
||||
}
|
||||
updateConnectedNodes(sb, connectedNodes);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSwitch(Switch sw, SwitchPayload sp, long rsId) {
|
||||
Set<SwitchConfiguration> newConfigs = new HashSet<>();
|
||||
Set<PathNode> connectedNodes = new HashSet<>();
|
||||
for (var configData : sp.possibleConfigurations) {
|
||||
Set<PathNode> nodes = new HashSet<>();
|
||||
for (var nodeData : configData.nodes) {
|
||||
var component = componentRepository.findByIdAndRailSystemId(nodeData.id, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path node id: " + nodeData.id));
|
||||
if (component instanceof PathNode pathNode && !pathNode.getId().equals(sw.getId())) {
|
||||
nodes.add(pathNode);
|
||||
connectedNodes.add(pathNode);
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path node: " + nodeData.id);
|
||||
}
|
||||
}
|
||||
if (nodes.size() != 2) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Switch configuration doesn't have required 2 path nodes.");
|
||||
}
|
||||
newConfigs.add(new SwitchConfiguration(sw, nodes));
|
||||
}
|
||||
if (newConfigs.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "There must be at least one switch configuration.");
|
||||
}
|
||||
|
||||
Set<PathNode> nodesToRemove = new HashSet<>(p.getConnectedNodes());
|
||||
nodesToRemove.removeAll(newNodes);
|
||||
sw.getPossibleConfigurations().retainAll(newConfigs);
|
||||
sw.getPossibleConfigurations().addAll(newConfigs);
|
||||
|
||||
Set<PathNode> nodesToAdd = new HashSet<>(newNodes);
|
||||
nodesToAdd.removeAll(p.getConnectedNodes());
|
||||
// A switch's connectedNodes are derived from its set of possible configurations.
|
||||
// So now we need to update all connected nodes to match the set of configurations.
|
||||
updateConnectedNodes(sw, connectedNodes);
|
||||
}
|
||||
|
||||
p.getConnectedNodes().removeAll(nodesToRemove);
|
||||
p.getConnectedNodes().addAll(nodesToAdd);
|
||||
for (var node : nodesToRemove) {
|
||||
node.getConnectedNodes().remove(p);
|
||||
private void updateConnectedNodes(PathNode owner, Set<PathNode> newNodes) {
|
||||
Set<PathNode> disconnected = new HashSet<>(owner.getConnectedNodes());
|
||||
disconnected.removeAll(newNodes);
|
||||
|
||||
Set<PathNode> connected = new HashSet<>(newNodes);
|
||||
connected.removeAll(owner.getConnectedNodes());
|
||||
|
||||
owner.getConnectedNodes().retainAll(newNodes);
|
||||
owner.getConnectedNodes().addAll(newNodes);
|
||||
|
||||
for (var node : disconnected) {
|
||||
node.getConnectedNodes().remove(owner);
|
||||
componentRepository.save(node);
|
||||
}
|
||||
for (var node : nodesToAdd) {
|
||||
node.getConnectedNodes().add(p);
|
||||
for (var node : connected) {
|
||||
node.getConnectedNodes().add(owner);
|
||||
componentRepository.save(node);
|
||||
}
|
||||
componentRepository.saveAll(nodesToRemove);
|
||||
componentRepository.saveAll(nodesToAdd);
|
||||
p = componentRepository.save(p);
|
||||
return ComponentResponse.of(p);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue