diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/EventRepository.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/EventRepository.java index 2763000a..ae9ae1c4 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/EventRepository.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/EventRepository.java @@ -1,7 +1,9 @@ package ch.sbb.sferamock.messages.services; import ch.sbb.sferamock.adapters.sfera.model.v0201.G2BEventPayload; +import ch.sbb.sferamock.adapters.sfera.model.v0201.RelatedTrainInformation; import ch.sbb.sferamock.messages.common.XmlHelper; +import ch.sbb.sferamock.messages.model.TrainIdentification; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -10,6 +12,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.boot.ApplicationArguments; @@ -31,6 +35,36 @@ public EventRepository(XmlHelper xmlHelper) { this.xmlHelper = xmlHelper; } + private static String extractOperationalNumber(String filename) { + Pattern pattern = Pattern.compile(XML_REGEX); + Matcher matcher = pattern.matcher(filename); + if (matcher.find()) { + String directoryOperationalNumber = matcher.group(1); + String fileOperationalNumber = matcher.group(2); + if (directoryOperationalNumber != null && directoryOperationalNumber.equals(fileOperationalNumber)) { + return directoryOperationalNumber; + } + } + throw new RuntimeException("Operational number extraction in Event repository failed for file: " + filename); + } + + private static int extractOffsetMs(String filename) { + Pattern pattern = Pattern.compile(OFFSET_XML_REGEX); + Matcher matcher = pattern.matcher(filename); + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)); + } + throw new RuntimeException("Offset extraction in Event repository failed for file: " + filename); + } + + public Optional getRelatedTrainInformation(TrainIdentification trainIdentification) { + return Optional.ofNullable(events.get(trainIdentification.operationalNumber())) + .flatMap(trainEvents -> trainEvents.stream() + .map(event -> event.payload.getRelatedTrainInformation()) + .filter(Objects::nonNull) + .findFirst()); + } + @Override public void run(ApplicationArguments args) throws Exception { importEvents(); @@ -57,28 +91,6 @@ private void importEvents() throws IOException { } } - private static String extractOperationalNumber(String filename) { - Pattern pattern = Pattern.compile(XML_REGEX); - Matcher matcher = pattern.matcher(filename); - if (matcher.find()) { - String directoryOperationalNumber = matcher.group(1); - String fileOperationalNumber = matcher.group(2); - if (directoryOperationalNumber != null && directoryOperationalNumber.equals(fileOperationalNumber)) { - return directoryOperationalNumber; - } - } - throw new RuntimeException("Operational number extraction in Event repository failed for file: " + filename); - } - - private static int extractOffsetMs(String filename) { - Pattern pattern = Pattern.compile(OFFSET_XML_REGEX); - Matcher matcher = pattern.matcher(filename); - if (matcher.find()) { - return Integer.parseInt(matcher.group(1)); - } - throw new RuntimeException("Offset extraction in Event repository failed for file: " + filename); - } - public record Event(int offsetMs, G2BEventPayload payload) { } diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java index 38b095c1..75424cf0 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/JourneyProfileRepository.java @@ -36,6 +36,7 @@ public void run(ApplicationArguments args) throws Exception { importJps(); } + // company and startDate is ignored public Optional getJourneyProfile(TrainIdentification trainIdentification) { return Optional.ofNullable(journeyProfiles.get(trainIdentification.operationalNumber())); } diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java index 32f2fc0b..c87c44c5 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SegmentProfileRepository.java @@ -36,6 +36,7 @@ public void run(ApplicationArguments args) throws Exception { importSps(); } + // segment version and company is ignored public Optional getSegmentProfile(SegmentIdentification spId) { return Optional.ofNullable(segmentProfiles.get(spId.id())); } diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java index d3eb7c3f..47791d42 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/services/SferaApplicationService.java @@ -1,6 +1,7 @@ package ch.sbb.sferamock.messages.services; import ch.sbb.sferamock.adapters.sfera.model.v0201.JourneyProfile; +import ch.sbb.sferamock.adapters.sfera.model.v0201.RelatedTrainInformation; import ch.sbb.sferamock.adapters.sfera.model.v0201.SPZoneComplexType; import ch.sbb.sferamock.adapters.sfera.model.v0201.SegmentProfile; import ch.sbb.sferamock.adapters.sfera.model.v0201.TrainCharacteristics; @@ -31,10 +32,11 @@ public class SferaApplicationService { private final JourneyProfileRepository journeyProfileRepository; private final SegmentProfileRepository segmentProfileRepository; private final TrainCharacteristicsRepository trainCharacteristicsRepository; + private final EventRepository eventRepository; public SferaApplicationService(ReplyPublisher replyPublisher, RequestContextRepository requestContextRepository, OperationModeSelector operationModeSelector, RegistrationService registrationService, JourneyProfileRepository journeyProfileRepository, SegmentProfileRepository segmentProfileRepository, - TrainCharacteristicsRepository trainCharacteristicsRepository) { + TrainCharacteristicsRepository trainCharacteristicsRepository, EventRepository eventRepository) { this.replyPublisher = replyPublisher; this.requestContextRepository = requestContextRepository; this.operationModeSelector = operationModeSelector; @@ -42,6 +44,7 @@ public SferaApplicationService(ReplyPublisher replyPublisher, RequestContextRepo this.journeyProfileRepository = journeyProfileRepository; this.segmentProfileRepository = segmentProfileRepository; this.trainCharacteristicsRepository = trainCharacteristicsRepository; + this.eventRepository = eventRepository; } private static JourneyProfile unavailableJourneyProfile() { @@ -130,6 +133,23 @@ public void processTrainCharacteristicsRequest(List trainIdentifications, RequestContext requestContext) { + if (!registrationService.isRegistered(requestContext.clientId())) { + publishErrorMessageUnregisteredClient(requestContext); + return; + } + + var correlationId = UUID.randomUUID(); + requestContextRepository.storeRequestContext(correlationId, requestContext); + + List relatedTrainInformations = trainIdentifications.stream().map(trainIdentification -> + eventRepository.getRelatedTrainInformation(trainIdentification).orElse(null)) + .filter(Objects::nonNull) + .toList(); + + publishRelatedTrainInformations(relatedTrainInformations, correlationId, requestContext); + } + private void publishJourneyProfile(Optional journeyProfile, UUID correlationId, RequestContext requestContext) { requestContextRepository.getRequestContext(correlationId) .ifPresentOrElse(it -> publishJourneyProfileResponse(journeyProfile, it), () -> replyPublisher.publishErrorMessage(SferaErrorCodes.COULD_NOT_PROCESS_DATA, requestContext)); @@ -166,6 +186,20 @@ private void publishTrainCharacteristicsResponse(List trai } } + private void publishRelatedTrainInformations(List relatedTrainInformations, UUID correlationId, RequestContext requestContext) { + requestContextRepository.getRequestContext(correlationId) + .ifPresentOrElse(it -> publishRelatedTrainInformationsResponse(relatedTrainInformations, it), + () -> replyPublisher.publishErrorMessage(SferaErrorCodes.COULD_NOT_PROCESS_DATA, requestContext)); + } + + private void publishRelatedTrainInformationsResponse(List relatedTrainInformations, RequestContext requestContext) { + if (!relatedTrainInformations.isEmpty()) { + replyPublisher.publishRelatedTrainInformations(relatedTrainInformations, requestContext); + } else { + replyPublisher.publishErrorMessage(SferaErrorCodes.COULD_NOT_PROCESS_DATA, requestContext); + } + } + private void publishOk(RequestContext requestContext) { replyPublisher.publishOkMessage(requestContext); } diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java index 33dbd3e0..c96738d5 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/IncomingMessageAdapter.java @@ -2,6 +2,7 @@ import ch.sbb.sferamock.adapters.sfera.model.v0201.B2GRequest; import ch.sbb.sferamock.adapters.sfera.model.v0201.JPRequest; +import ch.sbb.sferamock.adapters.sfera.model.v0201.RelatedTrainInformationRequest; import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAB2GEventMessage; import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAB2GReplyMessage; import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAB2GRequestMessage; @@ -113,6 +114,10 @@ private void processB2GRequest(SFERAB2GRequestMessage request, String topic) { processTrainCharacteristicsRequest(b2GRequest.getTCRequest(), requestContext); return; } + if (b2GRequest != null && b2GRequest.getRelatedTrainInformationRequest() != null && !b2GRequest.getRelatedTrainInformationRequest().isEmpty()) { + processRelatedTrainInformationRequest(b2GRequest.getRelatedTrainInformationRequest(), requestContext); + return; + } log.warn("A B2G Request that is not a handshake should currently have exactly one jp or sp request. Request is ignored."); } } @@ -163,4 +168,12 @@ private void processTrainCharacteristicsRequest(List tcRequests, Requ new CompanyCode(tcRequest.getTCRUID()))).toList(); sferaApplicationService.processTrainCharacteristicsRequest(trainCharacteristicsIdentifications, requestContext); } + + private void processRelatedTrainInformationRequest(List relatedTrainInformationRequests, RequestContext requestContext) { + var trainIdentifications = relatedTrainInformationRequests.stream() + .map(relatedTrainInformationRequest -> relatedTrainInformationRequest.getTrainIdentification().getOTNID()) + .map(otnId -> new TrainIdentification(new CompanyCode(otnId.getCompany()), otnId.getOperationalTrainNumber(), XmlDateHelper.toLocalDate(otnId.getStartDate()))) + .toList(); + sferaApplicationService.processRelatedTrainInformationRequest(trainIdentifications, requestContext); + } } diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java index 8294d363..4753e2f8 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/ReplyPublisher.java @@ -3,6 +3,7 @@ import static org.springframework.util.MimeTypeUtils.APPLICATION_XML; import ch.sbb.sferamock.adapters.sfera.model.v0201.JourneyProfile; +import ch.sbb.sferamock.adapters.sfera.model.v0201.RelatedTrainInformation; import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAG2BReplyMessage; import ch.sbb.sferamock.adapters.sfera.model.v0201.SegmentProfile; import ch.sbb.sferamock.adapters.sfera.model.v0201.TrainCharacteristics; @@ -55,6 +56,12 @@ public void publishTrainCharacteristics(List trainCharacte publishReplyMessage(reply, requestContext); } + public void publishRelatedTrainInformations(List relatedTrainInformations, RequestContext requestContext) { + var header = sferaMessageCreator.createMessageHeader(UUID.randomUUID(), requestContext.tid(), requestContext.incomingMessageId()); + var reply = sferaMessageCreator.createRelatedTrainInformationReplyMessage(relatedTrainInformations, header); + publishReplyMessage(reply, requestContext); + } + public void publishHandshakeAcknowledge(OperationMode.Connectivity connectivity, OperationMode.Architecture architecture, RequestContext requestContext) { diff --git a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java index 8fc05d16..dfcb70fe 100644 --- a/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java +++ b/sfera-mock/src/main/java/ch/sbb/sferamock/messages/sfera/SferaMessageCreator.java @@ -12,6 +12,7 @@ import ch.sbb.sferamock.adapters.sfera.model.v0201.MessageHeader; import ch.sbb.sferamock.adapters.sfera.model.v0201.OTNIDComplexType; import ch.sbb.sferamock.adapters.sfera.model.v0201.Recipient; +import ch.sbb.sferamock.adapters.sfera.model.v0201.RelatedTrainInformation; import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAG2BEventMessage; import ch.sbb.sferamock.adapters.sfera.model.v0201.SFERAG2BReplyMessage; import ch.sbb.sferamock.adapters.sfera.model.v0201.SegmentProfile; @@ -195,6 +196,16 @@ public SFERAG2BReplyMessage createTrainCharacteristicsReplyMessage(List relatedTrainInformations, MessageHeader header) { + var result = new SFERAG2BReplyMessage(); + result.setMessageHeader(header); + var payload = new G2BReplyPayload(); + relatedTrainInformations.forEach(relatedTrainInformation -> relatedTrainInformation.getOwnTrain().setTrainIdentification(header.getTrainIdentification())); + payload.getRelatedTrainInformation().addAll(relatedTrainInformations); + result.setG2BReplyPayload(payload); + return result; + } + public SFERAG2BReplyMessage createOkMessage(MessageHeader header) { var result = new SFERAG2BReplyMessage(); result.setMessageHeader(header); diff --git a/webapp/src/app/sfera-observer/sfera-observer.component.ts b/webapp/src/app/sfera-observer/sfera-observer.component.ts index a9837d52..9b22d48b 100644 --- a/webapp/src/app/sfera-observer/sfera-observer.component.ts +++ b/webapp/src/app/sfera-observer/sfera-observer.component.ts @@ -141,13 +141,15 @@ export class SferaObserverComponent implements OnDestroy { return this.getSegmentProfiles(document); } else if (this.containsElement(document, 'TrainCharacteristics')) { return this.getTrainCharacteristics(document); + } else if (this.containsElement(document, 'RelatedTrainInformation')) { + return 'RelatedTrainInformation'; } } else if (type == "SFERA_B2G_RequestMessage") { if (this.isHandshakeRequest(document)) { return `HS-REQUEST`; } - const requestTypes = ['JP_Request', 'SP_Request', 'TC_Request'] + const requestTypes = ['JP_Request', 'SP_Request', 'TC_Request', 'RelatedTrainInformationRequest'] const requestedTypes: string[] = []; for (const requestType of requestTypes) {