From 0717811c7d6b9454886b37e0937105db283a9f0e Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 9 Oct 2023 13:34:23 +0200 Subject: [PATCH 1/2] Add validation for scheduled transit leg reference --- .../ScheduledTransitLegReference.java | 50 ++++++- .../ScheduledTransitLegReferenceTest.java | 128 ++++++++++++++++++ 2 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java diff --git a/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java b/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java index a54945413c7..e67b00c9507 100644 --- a/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java +++ b/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java @@ -11,6 +11,8 @@ import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.TransitService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A reference which can be used to rebuild an exact copy of a {@link ScheduledTransitLeg} using the @@ -23,30 +25,70 @@ public record ScheduledTransitLegReference( int toStopPositionInPattern ) implements LegReference { + private static final Logger LOG = LoggerFactory.getLogger(ScheduledTransitLegReference.class); + + /** + * Reconstruct a scheduled transit leg from this scheduled transit leg reference. + * Since the transit model could have been modified between the time the reference is created + * and the time the transit leg is reconstructed (either because new planned data have been + * rolled out, or because a realtime update has modified a trip), + * it may not be possible to reconstruct the leg. + * In this case the method returns null. + */ @Override public ScheduledTransitLeg getLeg(TransitService transitService) { Trip trip = transitService.getTripForId(tripId); - if (trip == null) { + LOG.info("Invalid transit leg reference: trip {} not found", tripId); return null; } TripPattern tripPattern = transitService.getPatternForTrip(trip, serviceDate); - - // no matching pattern found anywhere if (tripPattern == null) { + LOG.info( + "Invalid transit leg reference: trip pattern not found for trip {} and service date {} ", + tripId, + serviceDate + ); return null; } - Timetable timetable = transitService.getTimetableForTripPattern(tripPattern, serviceDate); + int numStops = tripPattern.numberOfStops(); + if (fromStopPositionInPattern >= numStops || toStopPositionInPattern >= numStops) { + LOG.info( + "Invalid transit leg reference: boarding stop {} or alighting stop {} is out of range" + + " in trip {} and service date {} ({} stops in trip pattern) ", + fromStopPositionInPattern, + toStopPositionInPattern, + tripId, + serviceDate, + numStops + ); + return null; + } + Timetable timetable = transitService.getTimetableForTripPattern(tripPattern, serviceDate); TripTimes tripTimes = timetable.getTripTimes(trip); + if (tripTimes == null) { + LOG.info( + "Invalid transit leg reference: trip times not found for trip {} and service date {} ", + tripId, + serviceDate + ); + return null; + } + if ( !transitService .getServiceCodesRunningForDate(serviceDate) .contains(tripTimes.getServiceCode()) ) { + LOG.info( + "Invalid transit leg reference: the trip {} does not run on service date {} ", + tripId, + serviceDate + ); return null; } diff --git a/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java new file mode 100644 index 00000000000..8b834537bb6 --- /dev/null +++ b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java @@ -0,0 +1,128 @@ +package org.opentripplanner.model.plan.legreference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.model.Timetable; +import org.opentripplanner.model.calendar.CalendarServiceData; +import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.model.plan.ScheduledTransitLeg; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.StopModel; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; + +class ScheduledTransitLegReferenceTest { + + private static final int SERVICE_CODE = 555; + private static final LocalDate SERVICE_DATE = LocalDate.of(2023, 1, 1); + public static final int NUMBER_OF_STOPS = 3; + private static TransitService transitService; + private static FeedScopedId tripId; + + @BeforeAll + static void buildTransitService() { + // build transit data + TripPattern tripPattern = TransitModelForTest + .tripPattern("1", TransitModelForTest.route(id("1")).build()) + .withStopPattern(TransitModelForTest.stopPattern(NUMBER_OF_STOPS)) + .build(); + Timetable timetable = tripPattern.getScheduledTimetable(); + Trip trip = TransitModelForTest.trip("1").build(); + tripId = trip.getId(); + TripTimes tripTimes = new TripTimes( + trip, + TransitModelForTest.stopTimesEvery5Minutes(5, trip, PlanTestConstants.T11_00), + new Deduplicator() + ); + tripTimes.setServiceCode(SERVICE_CODE); + timetable.addTripTimes(tripTimes); + + // build transit model + TransitModel transitModel = new TransitModel(StopModel.of().build(), new Deduplicator()); + transitModel.addTripPattern(tripPattern.getId(), tripPattern); + transitModel.getServiceCodes().put(tripPattern.getId(), SERVICE_CODE); + CalendarServiceData calendarServiceData = new CalendarServiceData(); + calendarServiceData.putServiceDatesForServiceId(tripPattern.getId(), List.of(SERVICE_DATE)); + transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP); + transitModel.index(); + + // build transit service + transitService = new DefaultTransitService(transitModel); + } + + @Test + void getLegFromReference() { + int boardAtStop = 0; + int alightAtStop = 1; + ScheduledTransitLegReference scheduledTransitLegReference = new ScheduledTransitLegReference( + tripId, + SERVICE_DATE, + boardAtStop, + alightAtStop + ); + ScheduledTransitLeg leg = scheduledTransitLegReference.getLeg(transitService); + assertNotNull(leg); + assertEquals(tripId, leg.getTrip().getId()); + assertEquals(SERVICE_DATE, leg.getServiceDate()); + assertEquals(boardAtStop, leg.getBoardStopPosInPattern()); + assertEquals(alightAtStop, leg.getAlightStopPosInPattern()); + } + + @Test + void getLegFromReferenceUnknownTrip() { + ScheduledTransitLegReference scheduledTransitLegReference = new ScheduledTransitLegReference( + FeedScopedId.ofNullable("XXX", "YYY"), + SERVICE_DATE, + 0, + 1 + ); + assertNull(scheduledTransitLegReference.getLeg(transitService)); + } + + @Test + void getLegFromReferenceInvalidServiceDate() { + ScheduledTransitLegReference scheduledTransitLegReference = new ScheduledTransitLegReference( + tripId, + LocalDate.EPOCH, + 0, + 1 + ); + assertNull(scheduledTransitLegReference.getLeg(transitService)); + } + + @Test + void getLegFromReferenceInvalidBoardingStop() { + ScheduledTransitLegReference scheduledTransitLegReference = new ScheduledTransitLegReference( + tripId, + SERVICE_DATE, + NUMBER_OF_STOPS, + 1 + ); + assertNull(scheduledTransitLegReference.getLeg(transitService)); + } + + @Test + void getLegFromReferenceInvalidAlightingStop() { + ScheduledTransitLegReference scheduledTransitLegReference = new ScheduledTransitLegReference( + tripId, + SERVICE_DATE, + 0, + NUMBER_OF_STOPS + ); + assertNull(scheduledTransitLegReference.getLeg(transitService)); + } +} From 1583f4df3e68aa5d7cf1b59b8fadf8bd1f3292fa Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 10 Oct 2023 14:28:45 +0200 Subject: [PATCH 2/2] Apply review suggestions --- .../opentripplanner/model/plan/legreference/LegReference.java | 2 ++ .../model/plan/legreference/ScheduledTransitLegReference.java | 4 +++- .../plan/legreference/ScheduledTransitLegReferenceTest.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/plan/legreference/LegReference.java b/src/main/java/org/opentripplanner/model/plan/legreference/LegReference.java index 983d3c9ca88..cbac4abe6a5 100644 --- a/src/main/java/org/opentripplanner/model/plan/legreference/LegReference.java +++ b/src/main/java/org/opentripplanner/model/plan/legreference/LegReference.java @@ -1,5 +1,6 @@ package org.opentripplanner.model.plan.legreference; +import javax.annotation.Nullable; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.transit.service.TransitService; @@ -7,5 +8,6 @@ * Marker interface for various types of leg references */ public interface LegReference { + @Nullable Leg getLeg(TransitService transitService); } diff --git a/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java b/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java index e67b00c9507..e5e2ab695a4 100644 --- a/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java +++ b/src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.time.ZoneId; +import javax.annotation.Nullable; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.Timetable; import org.opentripplanner.model.plan.ScheduledTransitLeg; @@ -16,7 +17,7 @@ /** * A reference which can be used to rebuild an exact copy of a {@link ScheduledTransitLeg} using the - * {@Link RoutingService} + * {@link org.opentripplanner.routing.api.RoutingService} */ public record ScheduledTransitLegReference( FeedScopedId tripId, @@ -36,6 +37,7 @@ public record ScheduledTransitLegReference( * In this case the method returns null. */ @Override + @Nullable public ScheduledTransitLeg getLeg(TransitService transitService) { Trip trip = transitService.getTripForId(tripId); if (trip == null) { diff --git a/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java index 8b834537bb6..ed54ac1bd44 100644 --- a/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java +++ b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java @@ -29,7 +29,7 @@ class ScheduledTransitLegReferenceTest { private static final int SERVICE_CODE = 555; private static final LocalDate SERVICE_DATE = LocalDate.of(2023, 1, 1); - public static final int NUMBER_OF_STOPS = 3; + private static final int NUMBER_OF_STOPS = 3; private static TransitService transitService; private static FeedScopedId tripId;