Added more component stuff in preparation for integrations.
This commit is contained in:
		
							parent
							
								
									1906111ab8
								
							
						
					
					
						commit
						ecd9549e77
					
				
							
								
								
									
										4
									
								
								pom.xml
								
								
								
								
							
							
						
						
									
										4
									
								
								pom.xml
								
								
								
								
							| 
						 | 
					@ -29,6 +29,10 @@
 | 
				
			||||||
			<groupId>org.springframework.boot</groupId>
 | 
								<groupId>org.springframework.boot</groupId>
 | 
				
			||||||
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
 | 
								<artifactId>spring-boot-starter-thymeleaf</artifactId>
 | 
				
			||||||
		</dependency>
 | 
							</dependency>
 | 
				
			||||||
 | 
							<dependency>
 | 
				
			||||||
 | 
								<groupId>org.springframework.boot</groupId>
 | 
				
			||||||
 | 
								<artifactId>spring-boot-starter-validation</artifactId>
 | 
				
			||||||
 | 
							</dependency>
 | 
				
			||||||
		<dependency>
 | 
							<dependency>
 | 
				
			||||||
			<groupId>org.springframework.boot</groupId>
 | 
								<groupId>org.springframework.boot</groupId>
 | 
				
			||||||
			<artifactId>spring-boot-devtools</artifactId>
 | 
								<artifactId>spring-boot-devtools</artifactId>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					package nl.andrewl.railsignalapi.dao;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.model.ComponentAccessToken;
 | 
				
			||||||
 | 
					import org.springframework.data.jpa.repository.JpaRepository;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					public interface ComponentAccessTokenRepository extends JpaRepository<ComponentAccessToken, Long> {
 | 
				
			||||||
 | 
						Iterable<ComponentAccessToken> findAllByTokenPrefix(String prefix);
 | 
				
			||||||
 | 
						boolean existsByLabel(String label);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					package nl.andrewl.railsignalapi.model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.AccessLevel;
 | 
				
			||||||
 | 
					import lombok.Getter;
 | 
				
			||||||
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.model.component.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.persistence.*;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A secure token that allows users to connect to up- and down-link sockets
 | 
				
			||||||
 | 
					 * of components. This token is passed as either a header or query param when
 | 
				
			||||||
 | 
					 * establishing a websocket connection, or as part of the connection init
 | 
				
			||||||
 | 
					 * packet for plain socket connections.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Entity
 | 
				
			||||||
 | 
					@NoArgsConstructor(access = AccessLevel.PROTECTED)
 | 
				
			||||||
 | 
					@Getter
 | 
				
			||||||
 | 
					public class ComponentAccessToken {
 | 
				
			||||||
 | 
						@Id
 | 
				
			||||||
 | 
						@GeneratedValue
 | 
				
			||||||
 | 
						private Long id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * The rail system that this token belongs to.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@ManyToOne(optional = false, fetch = FetchType.LAZY)
 | 
				
			||||||
 | 
						private RailSystem railSystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * A semantic label for this token.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Column(length = 63, unique = true)
 | 
				
			||||||
 | 
						private String label;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * A short prefix of the token, which is useful for speeding up lookup.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Column(nullable = false, length = 7)
 | 
				
			||||||
 | 
						private String tokenPrefix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * A salted, hashed version of the full token string.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Column(nullable = false, unique = true)
 | 
				
			||||||
 | 
						private String tokenHash;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * The set of components that this access token grants access to.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@ManyToMany
 | 
				
			||||||
 | 
						private Set<Component> components;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public ComponentAccessToken(RailSystem railSystem, String label, String tokenPrefix, String tokenHash, Set<Component> components) {
 | 
				
			||||||
 | 
							this.railSystem = railSystem;
 | 
				
			||||||
 | 
							this.label = label;
 | 
				
			||||||
 | 
							this.tokenPrefix = tokenPrefix;
 | 
				
			||||||
 | 
							this.tokenHash = tokenHash;
 | 
				
			||||||
 | 
							this.components = components;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,9 @@ package nl.andrewl.railsignalapi.model;
 | 
				
			||||||
import lombok.AccessLevel;
 | 
					import lombok.AccessLevel;
 | 
				
			||||||
import lombok.Getter;
 | 
					import lombok.Getter;
 | 
				
			||||||
import lombok.NoArgsConstructor;
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
 | 
					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.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,19 +16,12 @@ import javax.persistence.*;
 | 
				
			||||||
@Entity
 | 
					@Entity
 | 
				
			||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
 | 
					@NoArgsConstructor(access = AccessLevel.PROTECTED)
 | 
				
			||||||
@Getter
 | 
					@Getter
 | 
				
			||||||
public class Label {
 | 
					public class Label extends Component {
 | 
				
			||||||
	@Id
 | 
						@Column(nullable = false)
 | 
				
			||||||
	@GeneratedValue
 | 
					 | 
				
			||||||
	private Long id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@ManyToOne(optional = false, fetch = FetchType.LAZY)
 | 
					 | 
				
			||||||
	private RailSystem railSystem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Column(nullable = false, length = 63)
 | 
					 | 
				
			||||||
	private String text;
 | 
						private String text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Label(RailSystem rs, String text) {
 | 
						public Label(RailSystem rs, Position position, String name, String text) {
 | 
				
			||||||
		this.railSystem = rs;
 | 
							super(rs, position, name, ComponentType.LABEL);
 | 
				
			||||||
		this.text = text;
 | 
							this.text = text;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,9 +9,9 @@ import nl.andrewl.railsignalapi.model.RailSystem;
 | 
				
			||||||
import javax.persistence.*;
 | 
					import javax.persistence.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Represents a physical component of the rail system that the API can interact
 | 
					 * Represents component of the rail system that exists in the system's world,
 | 
				
			||||||
 * with, and send or receive data from. For example, a signal, switch, or
 | 
					 * at a specific location. Any component that exists in the rail system extends
 | 
				
			||||||
 * detector.
 | 
					 * from this parent entity.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Entity
 | 
					@Entity
 | 
				
			||||||
@Inheritance(strategy = InheritanceType.JOINED)
 | 
					@Inheritance(strategy = InheritanceType.JOINED)
 | 
				
			||||||
| 
						 | 
					@ -51,9 +51,9 @@ public abstract class Component {
 | 
				
			||||||
	 * Whether this component is online, meaning that an in-world device is
 | 
						 * Whether this component is online, meaning that an in-world device is
 | 
				
			||||||
	 * currently connected to relay information regarding this component.
 | 
						 * currently connected to relay information regarding this component.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Column(nullable = false)
 | 
						@Column
 | 
				
			||||||
	@Setter
 | 
						@Setter
 | 
				
			||||||
	private boolean online = false;
 | 
						private Boolean online = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Component(RailSystem railSystem, Position position, String name, ComponentType type) {
 | 
						public Component(RailSystem railSystem, Position position, String name, ComponentType type) {
 | 
				
			||||||
		this.railSystem = railSystem;
 | 
							this.railSystem = railSystem;
 | 
				
			||||||
| 
						 | 
					@ -65,7 +65,8 @@ public abstract class Component {
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean equals(Object o) {
 | 
						public boolean equals(Object o) {
 | 
				
			||||||
		if (this == o) return true;
 | 
							if (this == o) return true;
 | 
				
			||||||
		return o instanceof Component c && this.id != null && this.id.equals(c.id);
 | 
							if (!(o instanceof Component c)) return false;
 | 
				
			||||||
 | 
							return (this.id != null && this.id.equals(c.id)) || this.name.equals(c.name);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,5 +3,6 @@ package nl.andrewl.railsignalapi.model.component;
 | 
				
			||||||
public enum ComponentType {
 | 
					public enum ComponentType {
 | 
				
			||||||
	SIGNAL,
 | 
						SIGNAL,
 | 
				
			||||||
	SWITCH,
 | 
						SWITCH,
 | 
				
			||||||
	SEGMENT_BOUNDARY
 | 
						SEGMENT_BOUNDARY,
 | 
				
			||||||
 | 
						LABEL
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ import lombok.Data;
 | 
				
			||||||
import lombok.NoArgsConstructor;
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.persistence.Embeddable;
 | 
					import javax.persistence.Embeddable;
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A three-dimensional position for a component within a system.
 | 
					 * A three-dimensional position for a component within a system.
 | 
				
			||||||
| 
						 | 
					@ -14,7 +15,10 @@ import javax.persistence.Embeddable;
 | 
				
			||||||
@AllArgsConstructor
 | 
					@AllArgsConstructor
 | 
				
			||||||
@NoArgsConstructor
 | 
					@NoArgsConstructor
 | 
				
			||||||
public class Position {
 | 
					public class Position {
 | 
				
			||||||
 | 
						@NotNull
 | 
				
			||||||
	private double x;
 | 
						private double x;
 | 
				
			||||||
 | 
						@NotNull
 | 
				
			||||||
	private double y;
 | 
						private double y;
 | 
				
			||||||
 | 
						@NotNull
 | 
				
			||||||
	private double z;
 | 
						private double z;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,10 @@ import org.springframework.stereotype.Controller;
 | 
				
			||||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
					import org.springframework.web.bind.annotation.GetMapping;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					import org.springframework.web.bind.annotation.RequestMapping;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Helper controller that redirects some common starting points to our embedded
 | 
				
			||||||
 | 
					 * web app's index page.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
@Controller
 | 
					@Controller
 | 
				
			||||||
@RequestMapping(path = {"/", "/app", "/home", "/index.html", "/index"})
 | 
					@RequestMapping(path = {"/", "/app", "/home", "/index.html", "/index"})
 | 
				
			||||||
public class IndexPageController {
 | 
					public class IndexPageController {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,15 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.rest;
 | 
					package nl.andrewl.railsignalapi.rest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
					 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
 | 
					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.ComponentResponse;
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.service.ComponentCreationService;
 | 
				
			||||||
import nl.andrewl.railsignalapi.service.ComponentService;
 | 
					import nl.andrewl.railsignalapi.service.ComponentService;
 | 
				
			||||||
import org.springframework.http.ResponseEntity;
 | 
					import org.springframework.http.ResponseEntity;
 | 
				
			||||||
import org.springframework.web.bind.annotation.*;
 | 
					import org.springframework.web.bind.annotation.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.Valid;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@RestController
 | 
					@RestController
 | 
				
			||||||
| 
						 | 
					@ -15,6 +17,7 @@ import java.util.List;
 | 
				
			||||||
@RequiredArgsConstructor
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
public class ComponentsApiController {
 | 
					public class ComponentsApiController {
 | 
				
			||||||
	private final ComponentService componentService;
 | 
						private final ComponentService componentService;
 | 
				
			||||||
 | 
						private final ComponentCreationService componentCreationService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@GetMapping
 | 
						@GetMapping
 | 
				
			||||||
	public List<ComponentResponse> getAllComponents(@PathVariable long rsId) {
 | 
						public List<ComponentResponse> getAllComponents(@PathVariable long rsId) {
 | 
				
			||||||
| 
						 | 
					@ -27,8 +30,8 @@ public class ComponentsApiController {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@PostMapping
 | 
						@PostMapping
 | 
				
			||||||
	public ComponentResponse createComponent(@PathVariable long rsId, @RequestBody ObjectNode data) {
 | 
						public ComponentResponse createComponent(@PathVariable long rsId, @RequestBody ComponentPayload payload) {
 | 
				
			||||||
		return componentService.create(rsId, data);
 | 
							return componentCreationService.create(rsId, payload);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@DeleteMapping(path = "/{cId}")
 | 
						@DeleteMapping(path = "/{cId}")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,27 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.rest.dto.component.in;
 | 
					package nl.andrewl.railsignalapi.rest.dto.component.in;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonSubTypes;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.model.Label;
 | 
				
			||||||
import nl.andrewl.railsignalapi.model.component.Position;
 | 
					import nl.andrewl.railsignalapi.model.component.Position;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotBlank;
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonIgnoreProperties(ignoreUnknown = true)
 | 
				
			||||||
 | 
					@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true)
 | 
				
			||||||
 | 
					@JsonSubTypes({
 | 
				
			||||||
 | 
							@JsonSubTypes.Type(value = SignalPayload.class, name = "SIGNAL"),
 | 
				
			||||||
 | 
							@JsonSubTypes.Type(value = SwitchPayload.class, name = "SWITCH"),
 | 
				
			||||||
 | 
							@JsonSubTypes.Type(value = SegmentBoundaryPayload.class, name = "SEGMENT_BOUNDARY"),
 | 
				
			||||||
 | 
							@JsonSubTypes.Type(value = Label.class, name = "LABEL")
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
public abstract class ComponentPayload {
 | 
					public abstract class ComponentPayload {
 | 
				
			||||||
 | 
						@NotNull @NotBlank
 | 
				
			||||||
	public String name;
 | 
						public String name;
 | 
				
			||||||
 | 
						@NotNull @NotBlank
 | 
				
			||||||
	public String type;
 | 
						public String type;
 | 
				
			||||||
 | 
						@NotNull
 | 
				
			||||||
	public Position position;
 | 
						public Position position;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					package nl.andrewl.railsignalapi.rest.dto.component.in;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotBlank;
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class LabelPayload extends ComponentPayload {
 | 
				
			||||||
 | 
						@NotNull @NotBlank
 | 
				
			||||||
 | 
						public String text;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					package nl.andrewl.railsignalapi.rest.dto.component.in;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotEmpty;
 | 
				
			||||||
 | 
					import javax.validation.constraints.Size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class SegmentBoundaryPayload extends ComponentPayload {
 | 
				
			||||||
 | 
						@NotEmpty @Size(min = 1, max = 2)
 | 
				
			||||||
 | 
						public SegmentPayload[] segments;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static class SegmentPayload {
 | 
				
			||||||
 | 
							public long id;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,11 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.rest.dto.component.in;
 | 
					package nl.andrewl.railsignalapi.rest.dto.component.in;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class SignalPayload extends ComponentPayload {
 | 
					public class SignalPayload extends ComponentPayload {
 | 
				
			||||||
	public long segmentId;
 | 
						@NotNull
 | 
				
			||||||
 | 
						public SegmentPayload segment;
 | 
				
			||||||
 | 
						public static class SegmentPayload {
 | 
				
			||||||
 | 
							public long id;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					package nl.andrewl.railsignalapi.rest.dto.component.in;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotEmpty;
 | 
				
			||||||
 | 
					import javax.validation.constraints.Size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class SwitchPayload extends ComponentPayload {
 | 
				
			||||||
 | 
						@NotEmpty @Size(min = 2, max = 10)
 | 
				
			||||||
 | 
						public SwitchConfigurationPayload[] possibleConfigurations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static class SwitchConfigurationPayload {
 | 
				
			||||||
 | 
							@NotEmpty @Size(min = 2, max = 10)
 | 
				
			||||||
 | 
							public NodePayload[] nodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public static class NodePayload {
 | 
				
			||||||
 | 
								public long id;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,27 +1,37 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
 | 
					package nl.andrewl.railsignalapi.rest.dto.component.out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.model.Label;
 | 
				
			||||||
import nl.andrewl.railsignalapi.model.component.*;
 | 
					import nl.andrewl.railsignalapi.model.component.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The base class for any component's API response object.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
public abstract class ComponentResponse {
 | 
					public abstract class ComponentResponse {
 | 
				
			||||||
	public long id;
 | 
						public long id;
 | 
				
			||||||
	public Position position;
 | 
						public Position position;
 | 
				
			||||||
	public String name;
 | 
						public String name;
 | 
				
			||||||
	public String type;
 | 
						public String type;
 | 
				
			||||||
	public boolean online;
 | 
						public Boolean online;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public ComponentResponse(Component c) {
 | 
						public ComponentResponse(Component c) {
 | 
				
			||||||
		this.id = c.getId();
 | 
							this.id = c.getId();
 | 
				
			||||||
		this.position = c.getPosition();
 | 
							this.position = c.getPosition();
 | 
				
			||||||
		this.name = c.getName();
 | 
							this.name = c.getName();
 | 
				
			||||||
		this.type = c.getType().name();
 | 
							this.type = c.getType().name();
 | 
				
			||||||
		this.online = c.isOnline();
 | 
							this.online = c.getOnline();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Builds a full response for a component of any type.
 | 
				
			||||||
 | 
						 * @param c The component to build a response for.
 | 
				
			||||||
 | 
						 * @return The response.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public static ComponentResponse of(Component c) {
 | 
						public static ComponentResponse of(Component c) {
 | 
				
			||||||
		return switch (c.getType()) {
 | 
							return switch (c.getType()) {
 | 
				
			||||||
			case SIGNAL -> new SignalResponse((Signal) c);
 | 
								case SIGNAL -> new SignalResponse((Signal) c);
 | 
				
			||||||
			case SWITCH -> new SwitchResponse((Switch) c);
 | 
								case SWITCH -> new SwitchResponse((Switch) c);
 | 
				
			||||||
			case SEGMENT_BOUNDARY -> new SegmentBoundaryNodeResponse((SegmentBoundaryNode) c);
 | 
								case SEGMENT_BOUNDARY -> new SegmentBoundaryNodeResponse((SegmentBoundaryNode) c);
 | 
				
			||||||
 | 
								case LABEL -> new LabelResponse((Label) c);
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					package nl.andrewl.railsignalapi.rest.dto.component.out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.model.Label;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class LabelResponse extends ComponentResponse {
 | 
				
			||||||
 | 
						public String text;
 | 
				
			||||||
 | 
						public LabelResponse(Label lbl) {
 | 
				
			||||||
 | 
							super(lbl);
 | 
				
			||||||
 | 
							this.text = lbl.getText();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ public record SimpleComponentResponse (
 | 
				
			||||||
		Position position,
 | 
							Position position,
 | 
				
			||||||
		String name,
 | 
							String name,
 | 
				
			||||||
		String type,
 | 
							String type,
 | 
				
			||||||
		boolean online
 | 
							Boolean online
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	public SimpleComponentResponse(Component c) {
 | 
						public SimpleComponentResponse(Component c) {
 | 
				
			||||||
		this(
 | 
							this(
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@ public record SimpleComponentResponse (
 | 
				
			||||||
				c.getPosition(),
 | 
									c.getPosition(),
 | 
				
			||||||
				c.getName(),
 | 
									c.getName(),
 | 
				
			||||||
				c.getType().name(),
 | 
									c.getType().name(),
 | 
				
			||||||
				c.isOnline()
 | 
									c.getOnline()
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,101 @@
 | 
				
			||||||
 | 
					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.model.Label;
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.model.RailSystem;
 | 
				
			||||||
 | 
					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 org.springframework.http.HttpStatus;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
 | 
					import org.springframework.web.server.ResponseStatusException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashSet;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
 | 
					public class ComponentCreationService {
 | 
				
			||||||
 | 
						private final RailSystemRepository railSystemRepository;
 | 
				
			||||||
 | 
						private final ComponentRepository<Component> componentRepository;
 | 
				
			||||||
 | 
						private final SegmentRepository segmentRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Transactional
 | 
				
			||||||
 | 
						public ComponentResponse create(long rsId, ComponentPayload payload) {
 | 
				
			||||||
 | 
							RailSystem rs = railSystemRepository.findById(rsId)
 | 
				
			||||||
 | 
									.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
				
			||||||
 | 
							String name = payload.name;
 | 
				
			||||||
 | 
							if (name == null || name.isBlank()) {
 | 
				
			||||||
 | 
								throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing required name.");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (componentRepository.existsByNameAndRailSystem(name, rs)) {
 | 
				
			||||||
 | 
								throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with that name already exists.");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ComponentType type;
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								type = ComponentType.valueOf(payload.type);
 | 
				
			||||||
 | 
							} catch (IllegalArgumentException e) {
 | 
				
			||||||
 | 
								throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid component type.", e);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Component c = switch (type) {
 | 
				
			||||||
 | 
								case SIGNAL -> createSignal(rs, (SignalPayload) payload);
 | 
				
			||||||
 | 
								case SWITCH -> createSwitch(rs, (SwitchPayload) payload);
 | 
				
			||||||
 | 
								case SEGMENT_BOUNDARY -> createSegmentBoundary(rs, (SegmentBoundaryPayload) payload);
 | 
				
			||||||
 | 
								case LABEL -> createLabel(rs, (LabelPayload) payload);
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							c = componentRepository.save(c);
 | 
				
			||||||
 | 
							return ComponentResponse.of(c);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Component createLabel(RailSystem rs, LabelPayload payload) {
 | 
				
			||||||
 | 
							return new Label(rs, payload.position, payload.name, payload.text);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Component createSignal(RailSystem rs, SignalPayload payload) {
 | 
				
			||||||
 | 
							long segmentId = payload.segment.id;
 | 
				
			||||||
 | 
							Segment segment = segmentRepository.findById(segmentId)
 | 
				
			||||||
 | 
									.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
				
			||||||
 | 
							return new Signal(rs, payload.position, payload.name, segment);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Component createSwitch(RailSystem rs, SwitchPayload payload) {
 | 
				
			||||||
 | 
							Switch s = new Switch(rs, payload.position, payload.name, new HashSet<>(), new HashSet<>(), null);
 | 
				
			||||||
 | 
							for (var config : payload.possibleConfigurations) {
 | 
				
			||||||
 | 
								Set<PathNode> pathNodes = new HashSet<>();
 | 
				
			||||||
 | 
								for (var node : config.nodes) {
 | 
				
			||||||
 | 
									Component c = componentRepository.findById(node.id)
 | 
				
			||||||
 | 
											.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
				
			||||||
 | 
									if (c instanceof PathNode pathNode) {
 | 
				
			||||||
 | 
										pathNodes.add(pathNode);
 | 
				
			||||||
 | 
										s.getConnectedNodes().add(pathNode);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id " + node.id + " does not refer to a PathNode component.");
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								s.getPossibleConfigurations().add(new SwitchConfiguration(s, pathNodes));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (s.getPossibleConfigurations().size() < 2) {
 | 
				
			||||||
 | 
								throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "At least two switch configurations are needed.");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return s;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Component createSegmentBoundary(RailSystem rs, SegmentBoundaryPayload payload) {
 | 
				
			||||||
 | 
							Set<Segment> segments = new HashSet<>();
 | 
				
			||||||
 | 
							for (var segmentP : payload.segments) {
 | 
				
			||||||
 | 
								Segment segment = segmentRepository.findById(segmentP.id)
 | 
				
			||||||
 | 
										.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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,11 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.service;
 | 
					package nl.andrewl.railsignalapi.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
					 | 
				
			||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
 | 
					 | 
				
			||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
					 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import nl.andrewl.railsignalapi.dao.ComponentRepository;
 | 
					import nl.andrewl.railsignalapi.dao.ComponentRepository;
 | 
				
			||||||
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
 | 
					import nl.andrewl.railsignalapi.dao.RailSystemRepository;
 | 
				
			||||||
import nl.andrewl.railsignalapi.dao.SegmentRepository;
 | 
					 | 
				
			||||||
import nl.andrewl.railsignalapi.dao.SwitchConfigurationRepository;
 | 
					import nl.andrewl.railsignalapi.dao.SwitchConfigurationRepository;
 | 
				
			||||||
import nl.andrewl.railsignalapi.model.RailSystem;
 | 
					import nl.andrewl.railsignalapi.model.component.Component;
 | 
				
			||||||
import nl.andrewl.railsignalapi.model.Segment;
 | 
					import nl.andrewl.railsignalapi.model.component.PathNode;
 | 
				
			||||||
import nl.andrewl.railsignalapi.model.component.*;
 | 
					 | 
				
			||||||
import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
 | 
					import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
 | 
				
			||||||
import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
 | 
					import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
 | 
				
			||||||
import org.springframework.http.HttpStatus;
 | 
					import org.springframework.http.HttpStatus;
 | 
				
			||||||
| 
						 | 
					@ -27,7 +22,6 @@ import java.util.Set;
 | 
				
			||||||
public class ComponentService {
 | 
					public class ComponentService {
 | 
				
			||||||
	private final ComponentRepository<Component> componentRepository;
 | 
						private final ComponentRepository<Component> componentRepository;
 | 
				
			||||||
	private final RailSystemRepository railSystemRepository;
 | 
						private final RailSystemRepository railSystemRepository;
 | 
				
			||||||
	private final SegmentRepository segmentRepository;
 | 
					 | 
				
			||||||
	private final SwitchConfigurationRepository switchConfigurationRepository;
 | 
						private final SwitchConfigurationRepository switchConfigurationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Transactional(readOnly = true)
 | 
						@Transactional(readOnly = true)
 | 
				
			||||||
| 
						 | 
					@ -48,86 +42,18 @@ public class ComponentService {
 | 
				
			||||||
	public void removeComponent(long rsId, long componentId) {
 | 
						public void removeComponent(long rsId, long componentId) {
 | 
				
			||||||
		var c = componentRepository.findByIdAndRailSystemId(componentId, rsId)
 | 
							var c = componentRepository.findByIdAndRailSystemId(componentId, rsId)
 | 
				
			||||||
				.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
									.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
				
			||||||
		// If this is a path node, check for and remove any switch configurations that use it.
 | 
					 | 
				
			||||||
		if (c instanceof PathNode p) {
 | 
							if (c instanceof PathNode p) {
 | 
				
			||||||
 | 
								// Remove all connections to other path nodes.
 | 
				
			||||||
 | 
								for (var connectedNode : p.getConnectedNodes()) {
 | 
				
			||||||
 | 
									connectedNode.getConnectedNodes().remove(p);
 | 
				
			||||||
 | 
									componentRepository.save(connectedNode);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Remove any switch configurations using this node.
 | 
				
			||||||
			switchConfigurationRepository.deleteAllByNodesContaining(p);
 | 
								switchConfigurationRepository.deleteAllByNodesContaining(p);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		componentRepository.delete(c);
 | 
							componentRepository.delete(c);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Transactional
 | 
					 | 
				
			||||||
	public ComponentResponse create(long rsId, ObjectNode data) {
 | 
					 | 
				
			||||||
		RailSystem rs = railSystemRepository.findById(rsId)
 | 
					 | 
				
			||||||
				.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
					 | 
				
			||||||
		String type = data.get("type").asText();
 | 
					 | 
				
			||||||
		Position pos = new Position();
 | 
					 | 
				
			||||||
		pos.setX(data.get("position").get("x").asDouble());
 | 
					 | 
				
			||||||
		pos.setY(data.get("position").get("y").asDouble());
 | 
					 | 
				
			||||||
		pos.setZ(data.get("position").get("z").asDouble());
 | 
					 | 
				
			||||||
		String name = data.get("name").asText();
 | 
					 | 
				
			||||||
		if (name == null || name.isBlank()) {
 | 
					 | 
				
			||||||
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing required name.");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (componentRepository.existsByNameAndRailSystem(name, rs)) {
 | 
					 | 
				
			||||||
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with that name already exists.");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Component c = switch (type) {
 | 
					 | 
				
			||||||
			case "SIGNAL" -> createSignal(rs, pos, name, data);
 | 
					 | 
				
			||||||
			case "SWITCH" -> createSwitch(rs, pos, name, data);
 | 
					 | 
				
			||||||
			case "SEGMENT_BOUNDARY" -> createSegmentBoundary(rs, pos, name, data);
 | 
					 | 
				
			||||||
			default -> throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported component type: " + type);
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
		c = componentRepository.save(c);
 | 
					 | 
				
			||||||
		return ComponentResponse.of(c);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private Component createSignal(RailSystem rs, Position pos, String name, ObjectNode data) {
 | 
					 | 
				
			||||||
		long segmentId = data.get("segment").get("id").asLong();
 | 
					 | 
				
			||||||
		Segment segment = segmentRepository.findById(segmentId)
 | 
					 | 
				
			||||||
				.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
					 | 
				
			||||||
		return new Signal(rs, pos, name, segment);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private Component createSwitch(RailSystem rs, Position pos, String name, ObjectNode data) {
 | 
					 | 
				
			||||||
		Switch s = new Switch(rs, pos, name, new HashSet<>(), new HashSet<>(), null);
 | 
					 | 
				
			||||||
		for (JsonNode configJson : data.withArray("possibleConfigurations")) {
 | 
					 | 
				
			||||||
			Set<PathNode> pathNodes = new HashSet<>();
 | 
					 | 
				
			||||||
			for (JsonNode pathNodeJson : configJson.withArray("nodes")) {
 | 
					 | 
				
			||||||
				long pathNodeId = pathNodeJson.get("id").asLong();
 | 
					 | 
				
			||||||
				Component c = componentRepository.findById(pathNodeId)
 | 
					 | 
				
			||||||
						.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
 | 
					 | 
				
			||||||
				if (c instanceof PathNode pathNode) {
 | 
					 | 
				
			||||||
					pathNodes.add(pathNode);
 | 
					 | 
				
			||||||
					s.getConnectedNodes().add(pathNode);
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id " + pathNodeId + " does not refer to a PathNode component.");
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			s.getPossibleConfigurations().add(new SwitchConfiguration(s, pathNodes));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (s.getPossibleConfigurations().size() < 2) {
 | 
					 | 
				
			||||||
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "At least two switch configurations are needed.");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return s;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private Component createSegmentBoundary(RailSystem rs, Position pos, String name, ObjectNode data) {
 | 
					 | 
				
			||||||
		ArrayNode segmentsNode = data.withArray("segments");
 | 
					 | 
				
			||||||
		Set<Segment> segments = new HashSet<>();
 | 
					 | 
				
			||||||
		for (JsonNode segmentNode : segmentsNode) {
 | 
					 | 
				
			||||||
			long segmentId = segmentNode.get("id").asLong();
 | 
					 | 
				
			||||||
			Segment segment = segmentRepository.findById(segmentId)
 | 
					 | 
				
			||||||
					.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, pos, name, new HashSet<>(), segments);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Transactional
 | 
						@Transactional
 | 
				
			||||||
	public ComponentResponse updatePath(long rsId, long cId, PathNodeUpdatePayload payload) {
 | 
						public ComponentResponse updatePath(long rsId, long cId, PathNodeUpdatePayload payload) {
 | 
				
			||||||
		var c = componentRepository.findByIdAndRailSystemId(cId, rsId)
 | 
							var c = componentRepository.findByIdAndRailSystemId(cId, rsId)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.websocket;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public record BranchUpdateMessage(long branchId, String status) {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -9,11 +9,13 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
 | 
				
			||||||
@Configuration
 | 
					@Configuration
 | 
				
			||||||
@EnableWebSocket
 | 
					@EnableWebSocket
 | 
				
			||||||
@RequiredArgsConstructor
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
public class WebSocketConfig implements WebSocketConfigurer {
 | 
					public class ComponentWebSocketConfig implements WebSocketConfigurer {
 | 
				
			||||||
	private final SignalWebSocketHandler webSocketHandler;
 | 
						private final SignalWebSocketHandler webSocketHandler;
 | 
				
			||||||
 | 
						private final ComponentWebSocketHandshakeInterceptor handshakeInterceptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
 | 
						public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
 | 
				
			||||||
		registry.addHandler(webSocketHandler, "/api/ws-signal");
 | 
							registry.addHandler(webSocketHandler, "/api/ws/component")
 | 
				
			||||||
 | 
									.addInterceptors(handshakeInterceptor);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					package nl.andrewl.railsignalapi.websocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.dao.ComponentRepository;
 | 
				
			||||||
 | 
					import nl.andrewl.railsignalapi.dao.RailSystemRepository;
 | 
				
			||||||
 | 
					import org.springframework.http.server.ServerHttpRequest;
 | 
				
			||||||
 | 
					import org.springframework.http.server.ServerHttpResponse;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					import org.springframework.web.socket.WebSocketHandler;
 | 
				
			||||||
 | 
					import org.springframework.web.socket.server.HandshakeInterceptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
 | 
					public class ComponentWebSocketHandshakeInterceptor implements HandshakeInterceptor {
 | 
				
			||||||
 | 
						private final RailSystemRepository railSystemRepository;
 | 
				
			||||||
 | 
						private final ComponentRepository<nl.andrewl.railsignalapi.model.component.Component> componentRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
 | 
				
			||||||
 | 
							String[] queryParams = request.getURI().getQuery().split("&");
 | 
				
			||||||
 | 
							System.out.println(Arrays.toString(queryParams));
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,8 +0,0 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.websocket;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public record SignalUpdateMessage(
 | 
					 | 
				
			||||||
		long signalId,
 | 
					 | 
				
			||||||
		long fromBranchId,
 | 
					 | 
				
			||||||
		long toBranchId,
 | 
					 | 
				
			||||||
		String type
 | 
					 | 
				
			||||||
) {}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,15 +0,0 @@
 | 
				
			||||||
package nl.andrewl.railsignalapi.websocket;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public enum SignalUpdateType {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Indicates the beginning of a train's transition between two signalled
 | 
					 | 
				
			||||||
	 * sections of rail.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	BEGIN,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Indicates the end of a train's transition between two signalled sections
 | 
					 | 
				
			||||||
	 * of rail.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	END
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ public class SignalWebSocketHandler extends TextWebSocketHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@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);
 | 
				
			||||||
		//signalService.handleSignalUpdate(msg);
 | 
							//signalService.handleSignalUpdate(msg);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue