From c10fb0122bf4014115012564cabfbb88f097cb3d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 17 Dec 2024 10:52:30 +0200 Subject: [PATCH 01/22] Draft for injecting defaults into GraphQL schema --- .../gtfs/DefaultValueDirectiveWiring.java | 47 +++++++++++++++++++ .../apis/gtfs/GtfsGraphQLIndex.java | 20 ++++---- .../opentripplanner/apis/gtfs/schema.graphqls | 8 +++- .../apis/gtfs/GraphQLFormattingTest.java | 2 +- .../apis/gtfs/GraphQLIndexTest.java | 7 +-- 5 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java new file mode 100644 index 00000000000..1a066bbeaf9 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java @@ -0,0 +1,47 @@ +package org.opentripplanner.apis.gtfs; + +import graphql.language.IntValue; +import graphql.language.Value; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.idl.SchemaDirectiveWiring; +import graphql.schema.idl.SchemaDirectiveWiringEnvironment; +import java.util.List; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; + +public class DefaultValueDirectiveWiring implements SchemaDirectiveWiring { + + private final RoutingPreferences defaultPreferences; + + public DefaultValueDirectiveWiring(RoutingPreferences defaultPreferences) { + this.defaultPreferences = defaultPreferences; + } + + @Override + public GraphQLInputObjectField onInputObjectField( + SchemaDirectiveWiringEnvironment environment + ) { + GraphQLInputObjectField field = environment.getElement(); + if (field.hasAppliedDirective("defaultValue")) { + String valueKey = environment + .getAppliedDirective("defaultValue") + .getArgument("valueKey") + .getValue(); + return field.transform(builder -> + builder + .defaultValueLiteral(getDefaultValueForKey(valueKey)) + .replaceDirectives(List.of(field.getDirective("defaultValue"))) + .build() + ); + } + return field; + } + + private Value getDefaultValueForKey(String key) { + switch (key) { + case "bicycleBoardCost": + return IntValue.of(defaultPreferences.bike().boardCost()); + default: + return null; + } + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 34e9b2f8346..4c3a094ca83 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -24,6 +24,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.datafetchers.AgencyImpl; import org.opentripplanner.apis.gtfs.datafetchers.AlertEntityTypeResolver; import org.opentripplanner.apis.gtfs.datafetchers.AlertImpl; @@ -87,6 +88,7 @@ import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.graphql.GraphQLResponseSerializer; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,14 +96,12 @@ class GtfsGraphQLIndex { static final Logger LOG = LoggerFactory.getLogger(GtfsGraphQLIndex.class); - private static final GraphQLSchema indexSchema = buildSchema(); - - protected static GraphQLSchema buildSchema() { + protected static GraphQLSchema buildSchema(@Nullable RoutingPreferences defaultPreferences) { try { URL url = Objects.requireNonNull(GtfsGraphQLIndex.class.getResource("schema.graphqls")); TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(url.openStream()); IntrospectionTypeWiring typeWiring = new IntrospectionTypeWiring(typeRegistry); - RuntimeWiring runtimeWiring = RuntimeWiring + RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring .newRuntimeWiring() .scalar(GraphQLScalars.DURATION_SCALAR) .scalar(GraphQLScalars.POLYLINE_SCALAR) @@ -180,10 +180,14 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(CurrencyImpl.class)) .type(typeWiring.build(FareProductUseImpl.class)) .type(typeWiring.build(DefaultFareProductImpl.class)) - .type(typeWiring.build(TripOccupancyImpl.class)) - .build(); + .type(typeWiring.build(TripOccupancyImpl.class)); + + if (defaultPreferences != null) { + runtimeWiringBuilder.directiveWiring(new DefaultValueDirectiveWiring(defaultPreferences)); + } + SchemaGenerator schemaGenerator = new SchemaGenerator(); - return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); + return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiringBuilder.build()); } catch (Exception e) { LOG.error("Unable to build GTFS GraphQL Schema", e); } @@ -210,7 +214,7 @@ static ExecutionResult getGraphQLExecutionResult( } GraphQL graphQL = GraphQL - .newGraphQL(indexSchema) + .newGraphQL(buildSchema(requestContext.defaultRouteRequest().preferences())) .instrumentation(instrumentation) .defaultDataFetcherExceptionHandler(new LoggingDataFetcherExceptionHandler()) .build(); diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index f92a8191018..b954325e4b3 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -12,6 +12,12 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION +"Default value is based on hard-coded defaults and configuration." +directive @defaultValue( + "Key used for providing the default for the input field." + valueKey: String! + ) on INPUT_FIELD_DEFINITION + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -3707,7 +3713,7 @@ input BicycleParkingPreferencesInput { "Preferences related to travel with a bicycle." input BicyclePreferencesInput { "Cost of boarding a vehicle with a bicycle." - boardCost: Cost + boardCost: Cost @defaultValue(valueKey : "bicycleBoardCost") "What criteria should be used when optimizing a cycling route." optimization: CyclingOptimizationInput "Bicycle parking related preferences." diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java index 7c05fc8e361..1dcae08953f 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java @@ -17,7 +17,7 @@ public class GraphQLFormattingTest { @Test public void format() { String original = readFile(SCHEMA_FILE); - var schema = GtfsGraphQLIndex.buildSchema(); + var schema = GtfsGraphQLIndex.buildSchema(null); writeFile(SCHEMA_FILE, new SchemaPrinter().print(schema)); assertFileEquals(original, SCHEMA_FILE); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java index 0013f7df202..a925bc749cc 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java @@ -14,12 +14,13 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; public class GraphQLIndexTest { @Test public void build() { - var schema = GtfsGraphQLIndex.buildSchema(); + var schema = GtfsGraphQLIndex.buildSchema(new RoutingPreferences()); assertNotNull(schema); } @@ -27,12 +28,12 @@ public void build() { @ParameterizedTest(name = "\"{0}\" must be a an async fetcher") void asyncDataFetchers(String fieldName) { OTPFeature.AsyncGraphQLFetchers.testOn(() -> { - var schema = GtfsGraphQLIndex.buildSchema(); + var schema = GtfsGraphQLIndex.buildSchema(new RoutingPreferences()); var fetcher = getQueryType(fieldName, schema); assertSame(fetcher.getClass(), AsyncDataFetcher.class); }); OTPFeature.AsyncGraphQLFetchers.testOff(() -> { - var schema = GtfsGraphQLIndex.buildSchema(); + var schema = GtfsGraphQLIndex.buildSchema(new RoutingPreferences()); var fetcher = getQueryType(fieldName, schema); assertNotSame(fetcher.getClass(), AsyncDataFetcher.class); }); From 2c42d14dc4a336b9a805caea9eb40c4b6ed64923 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 17 Dec 2024 15:48:46 +0200 Subject: [PATCH 02/22] Don't rely on directive for injecting defaults --- .../gtfs/DefaultValueDirectiveWiring.java | 19 ++++++------------- .../opentripplanner/apis/gtfs/schema.graphqls | 8 +------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java index 1a066bbeaf9..359fbc8aaf4 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java @@ -5,7 +5,6 @@ import graphql.schema.GraphQLInputObjectField; import graphql.schema.idl.SchemaDirectiveWiring; import graphql.schema.idl.SchemaDirectiveWiringEnvironment; -import java.util.List; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; public class DefaultValueDirectiveWiring implements SchemaDirectiveWiring { @@ -21,24 +20,18 @@ public GraphQLInputObjectField onInputObjectField( SchemaDirectiveWiringEnvironment environment ) { GraphQLInputObjectField field = environment.getElement(); - if (field.hasAppliedDirective("defaultValue")) { - String valueKey = environment - .getAppliedDirective("defaultValue") - .getArgument("valueKey") - .getValue(); - return field.transform(builder -> - builder - .defaultValueLiteral(getDefaultValueForKey(valueKey)) - .replaceDirectives(List.of(field.getDirective("defaultValue"))) - .build() - ); + var parentName = environment.getNodeParentTree().toList().get(1).getName(); + var key = parentName + "_" + field.getName(); + var defaultValue = getDefaultValueForKey(key); + if (defaultValue != null) { + return field.transform(builder -> builder.defaultValueLiteral(defaultValue).build()); } return field; } private Value getDefaultValueForKey(String key) { switch (key) { - case "bicycleBoardCost": + case "BicyclePreferencesInput_boardCost": return IntValue.of(defaultPreferences.bike().boardCost()); default: return null; diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index b954325e4b3..f92a8191018 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -12,12 +12,6 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION -"Default value is based on hard-coded defaults and configuration." -directive @defaultValue( - "Key used for providing the default for the input field." - valueKey: String! - ) on INPUT_FIELD_DEFINITION - "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -3713,7 +3707,7 @@ input BicycleParkingPreferencesInput { "Preferences related to travel with a bicycle." input BicyclePreferencesInput { "Cost of boarding a vehicle with a bicycle." - boardCost: Cost @defaultValue(valueKey : "bicycleBoardCost") + boardCost: Cost "What criteria should be used when optimizing a cycling route." optimization: CyclingOptimizationInput "Bicycle parking related preferences." From c51b975fddd3ce67a1fb1e11d69cbba6632ad8e5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 30 Dec 2024 17:12:54 +0200 Subject: [PATCH 03/22] Use dependency injection for providing GraphQL schema --- .../apis/gtfs/GraphQLRequestContext.java | 3 + .../apis/gtfs/GtfsGraphQLIndex.java | 173 +---------------- .../apis/gtfs/SchemaFactory.java | 183 ++++++++++++++++++ .../apis/gtfs/service/SchemaService.java | 22 +++ .../configure/SchemaServiceModule.java | 23 +++ .../api/OtpServerRequestContext.java | 4 + .../configure/ConstructApplication.java | 1 + .../ConstructApplicationFactory.java | 10 + .../configure/ConstructApplicationModule.java | 3 + .../server/DefaultServerRequestContext.java | 14 ++ .../opentripplanner/TestServerContext.java | 1 + .../apis/gtfs/GraphQLFormattingTest.java | 2 +- .../apis/gtfs/GraphQLIndexTest.java | 6 +- .../apis/gtfs/GraphQLIntegrationTest.java | 5 +- .../LegacyRouteRequestMapperTest.java | 6 +- .../routerequest/RouteRequestMapperTest.java | 6 +- .../mapping/TripRequestMapperTest.java | 1 + .../transit/speed_test/SpeedTest.java | 1 + 18 files changed, 285 insertions(+), 179 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java index c3ca214b62f..3f3261cbe66 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java @@ -1,5 +1,6 @@ package org.opentripplanner.apis.gtfs; +import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.routing.api.RoutingService; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.fares.FareService; @@ -17,6 +18,7 @@ public record GraphQLRequestContext( VehicleRentalService vehicleRentalService, VehicleParkingService vehicleParkingService, RealtimeVehicleService realTimeVehicleService, + SchemaService schemaService, GraphFinder graphFinder, RouteRequest defaultRouteRequest ) { @@ -28,6 +30,7 @@ public static GraphQLRequestContext ofServerContext(OtpServerRequestContext cont context.vehicleRentalService(), context.vehicleParkingService(), context.realtimeVehicleService(), + context.schemaService(), context.graphFinder(), context.defaultRouteRequest() ); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 4c3a094ca83..455a4c68042 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -7,193 +7,22 @@ import graphql.execution.AbortExecutionException; import graphql.execution.instrumentation.ChainedInstrumentation; import graphql.execution.instrumentation.Instrumentation; -import graphql.scalars.ExtendedScalars; -import graphql.schema.GraphQLSchema; -import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; import io.micrometer.core.instrument.Metrics; import jakarta.ws.rs.core.Response; -import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.datafetchers.AgencyImpl; -import org.opentripplanner.apis.gtfs.datafetchers.AlertEntityTypeResolver; -import org.opentripplanner.apis.gtfs.datafetchers.AlertImpl; -import org.opentripplanner.apis.gtfs.datafetchers.BikeParkImpl; -import org.opentripplanner.apis.gtfs.datafetchers.BikeRentalStationImpl; -import org.opentripplanner.apis.gtfs.datafetchers.BookingInfoImpl; -import org.opentripplanner.apis.gtfs.datafetchers.BookingTimeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.CarParkImpl; -import org.opentripplanner.apis.gtfs.datafetchers.ContactInfoImpl; -import org.opentripplanner.apis.gtfs.datafetchers.CoordinatesImpl; -import org.opentripplanner.apis.gtfs.datafetchers.CurrencyImpl; -import org.opentripplanner.apis.gtfs.datafetchers.DefaultFareProductImpl; -import org.opentripplanner.apis.gtfs.datafetchers.DepartureRowImpl; -import org.opentripplanner.apis.gtfs.datafetchers.FareProductTypeResolver; -import org.opentripplanner.apis.gtfs.datafetchers.FareProductUseImpl; -import org.opentripplanner.apis.gtfs.datafetchers.FeedImpl; -import org.opentripplanner.apis.gtfs.datafetchers.GeometryImpl; -import org.opentripplanner.apis.gtfs.datafetchers.ItineraryImpl; -import org.opentripplanner.apis.gtfs.datafetchers.LegImpl; -import org.opentripplanner.apis.gtfs.datafetchers.MoneyImpl; -import org.opentripplanner.apis.gtfs.datafetchers.NodeTypeResolver; -import org.opentripplanner.apis.gtfs.datafetchers.OpeningHoursImpl; -import org.opentripplanner.apis.gtfs.datafetchers.PatternImpl; -import org.opentripplanner.apis.gtfs.datafetchers.PlaceImpl; -import org.opentripplanner.apis.gtfs.datafetchers.PlaceInterfaceTypeResolver; -import org.opentripplanner.apis.gtfs.datafetchers.PlanConnectionImpl; -import org.opentripplanner.apis.gtfs.datafetchers.PlanImpl; -import org.opentripplanner.apis.gtfs.datafetchers.QueryTypeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.RentalPlaceTypeResolver; -import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleImpl; -import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleTypeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.RideHailingEstimateImpl; -import org.opentripplanner.apis.gtfs.datafetchers.RouteImpl; -import org.opentripplanner.apis.gtfs.datafetchers.RouteTypeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.RoutingErrorImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StopGeometriesImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StopImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StopOnRouteImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StopOnTripImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StopRelationshipImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StoptimeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StoptimesInPatternImpl; -import org.opentripplanner.apis.gtfs.datafetchers.SystemNoticeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.TicketTypeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.TranslatedStringImpl; -import org.opentripplanner.apis.gtfs.datafetchers.TripImpl; -import org.opentripplanner.apis.gtfs.datafetchers.TripOccupancyImpl; -import org.opentripplanner.apis.gtfs.datafetchers.UnknownImpl; -import org.opentripplanner.apis.gtfs.datafetchers.VehicleParkingImpl; -import org.opentripplanner.apis.gtfs.datafetchers.VehiclePositionImpl; -import org.opentripplanner.apis.gtfs.datafetchers.VehicleRentalNetworkImpl; -import org.opentripplanner.apis.gtfs.datafetchers.VehicleRentalStationImpl; -import org.opentripplanner.apis.gtfs.datafetchers.debugOutputImpl; -import org.opentripplanner.apis.gtfs.datafetchers.elevationProfileComponentImpl; -import org.opentripplanner.apis.gtfs.datafetchers.placeAtDistanceImpl; -import org.opentripplanner.apis.gtfs.datafetchers.serviceTimeRangeImpl; -import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; -import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; -import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.support.graphql.LoggingDataFetcherExceptionHandler; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.graphql.GraphQLResponseSerializer; -import org.opentripplanner.routing.api.request.preference.RoutingPreferences; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; class GtfsGraphQLIndex { - static final Logger LOG = LoggerFactory.getLogger(GtfsGraphQLIndex.class); - - protected static GraphQLSchema buildSchema(@Nullable RoutingPreferences defaultPreferences) { - try { - URL url = Objects.requireNonNull(GtfsGraphQLIndex.class.getResource("schema.graphqls")); - TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(url.openStream()); - IntrospectionTypeWiring typeWiring = new IntrospectionTypeWiring(typeRegistry); - RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring - .newRuntimeWiring() - .scalar(GraphQLScalars.DURATION_SCALAR) - .scalar(GraphQLScalars.POLYLINE_SCALAR) - .scalar(GraphQLScalars.GEOJSON_SCALAR) - .scalar(GraphQLScalars.GRAPHQL_ID_SCALAR) - .scalar(GraphQLScalars.GRAMS_SCALAR) - .scalar(GraphQLScalars.OFFSET_DATETIME_SCALAR) - .scalar(GraphQLScalars.RATIO_SCALAR) - .scalar(GraphQLScalars.COORDINATE_VALUE_SCALAR) - .scalar(GraphQLScalars.COST_SCALAR) - .scalar(GraphQLScalars.RELUCTANCE_SCALAR) - .scalar(GraphQLScalars.LOCAL_DATE_SCALAR) - .scalar(ExtendedScalars.GraphQLLong) - .scalar(ExtendedScalars.Locale) - .scalar( - ExtendedScalars - .newAliasedScalar("Speed") - .aliasedScalar(ExtendedScalars.NonNegativeFloat) - .build() - ) - .type("Node", type -> type.typeResolver(new NodeTypeResolver())) - .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) - .type("RentalPlace", type -> type.typeResolver(new RentalPlaceTypeResolver())) - .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) - .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) - .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) - .type(typeWiring.build(AgencyImpl.class)) - .type(typeWiring.build(AlertImpl.class)) - .type(typeWiring.build(BikeParkImpl.class)) - .type(typeWiring.build(VehicleParkingImpl.class)) - .type(typeWiring.build(BikeRentalStationImpl.class)) - .type(typeWiring.build(CarParkImpl.class)) - .type(typeWiring.build(CoordinatesImpl.class)) - .type(typeWiring.build(debugOutputImpl.class)) - .type(typeWiring.build(DepartureRowImpl.class)) - .type(typeWiring.build(elevationProfileComponentImpl.class)) - .type(typeWiring.build(FeedImpl.class)) - .type(typeWiring.build(GeometryImpl.class)) - .type(typeWiring.build(ItineraryImpl.class)) - .type(typeWiring.build(LegImpl.class)) - .type(typeWiring.build(PatternImpl.class)) - .type(typeWiring.build(PlaceImpl.class)) - .type(typeWiring.build(placeAtDistanceImpl.class)) - .type(typeWiring.build(PlanConnectionImpl.class)) - .type(typeWiring.build(PlanImpl.class)) - .type(typeWiring.build(QueryTypeImpl.class)) - .type(typeWiring.build(RouteImpl.class)) - .type(typeWiring.build(serviceTimeRangeImpl.class)) - .type(typeWiring.build(stepImpl.class)) - .type(typeWiring.build(StopImpl.class)) - .type(typeWiring.build(stopAtDistanceImpl.class)) - .type(typeWiring.build(StoptimeImpl.class)) - .type(typeWiring.build(StoptimesInPatternImpl.class)) - .type(typeWiring.build(TicketTypeImpl.class)) - .type(typeWiring.build(TranslatedStringImpl.class)) - .type(typeWiring.build(TripImpl.class)) - .type(typeWiring.build(SystemNoticeImpl.class)) - .type(typeWiring.build(ContactInfoImpl.class)) - .type(typeWiring.build(BookingTimeImpl.class)) - .type(typeWiring.build(BookingInfoImpl.class)) - .type(typeWiring.build(VehicleRentalStationImpl.class)) - .type(typeWiring.build(VehicleRentalNetworkImpl.class)) - .type(typeWiring.build(RentalVehicleImpl.class)) - .type(typeWiring.build(RentalVehicleTypeImpl.class)) - .type(typeWiring.build(StopOnRouteImpl.class)) - .type(typeWiring.build(StopOnTripImpl.class)) - .type(typeWiring.build(UnknownImpl.class)) - .type(typeWiring.build(RouteTypeImpl.class)) - .type(typeWiring.build(RoutingErrorImpl.class)) - .type(typeWiring.build(StopGeometriesImpl.class)) - .type(typeWiring.build(VehiclePositionImpl.class)) - .type(typeWiring.build(StopRelationshipImpl.class)) - .type(typeWiring.build(OpeningHoursImpl.class)) - .type(typeWiring.build(RideHailingEstimateImpl.class)) - .type(typeWiring.build(MoneyImpl.class)) - .type(typeWiring.build(CurrencyImpl.class)) - .type(typeWiring.build(FareProductUseImpl.class)) - .type(typeWiring.build(DefaultFareProductImpl.class)) - .type(typeWiring.build(TripOccupancyImpl.class)); - - if (defaultPreferences != null) { - runtimeWiringBuilder.directiveWiring(new DefaultValueDirectiveWiring(defaultPreferences)); - } - - SchemaGenerator schemaGenerator = new SchemaGenerator(); - return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiringBuilder.build()); - } catch (Exception e) { - LOG.error("Unable to build GTFS GraphQL Schema", e); - } - return null; - } - static ExecutionResult getGraphQLExecutionResult( String query, Map variables, @@ -214,7 +43,7 @@ static ExecutionResult getGraphQLExecutionResult( } GraphQL graphQL = GraphQL - .newGraphQL(buildSchema(requestContext.defaultRouteRequest().preferences())) + .newGraphQL(requestContext.schemaService().schema()) .instrumentation(instrumentation) .defaultDataFetcherExceptionHandler(new LoggingDataFetcherExceptionHandler()) .build(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java new file mode 100644 index 00000000000..30f00f55e2c --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -0,0 +1,183 @@ +package org.opentripplanner.apis.gtfs; + +import graphql.scalars.ExtendedScalars; +import graphql.schema.GraphQLSchema; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import java.net.URL; +import java.util.Objects; +import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.datafetchers.AgencyImpl; +import org.opentripplanner.apis.gtfs.datafetchers.AlertEntityTypeResolver; +import org.opentripplanner.apis.gtfs.datafetchers.AlertImpl; +import org.opentripplanner.apis.gtfs.datafetchers.BikeParkImpl; +import org.opentripplanner.apis.gtfs.datafetchers.BikeRentalStationImpl; +import org.opentripplanner.apis.gtfs.datafetchers.BookingInfoImpl; +import org.opentripplanner.apis.gtfs.datafetchers.BookingTimeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.CarParkImpl; +import org.opentripplanner.apis.gtfs.datafetchers.ContactInfoImpl; +import org.opentripplanner.apis.gtfs.datafetchers.CoordinatesImpl; +import org.opentripplanner.apis.gtfs.datafetchers.CurrencyImpl; +import org.opentripplanner.apis.gtfs.datafetchers.DefaultFareProductImpl; +import org.opentripplanner.apis.gtfs.datafetchers.DepartureRowImpl; +import org.opentripplanner.apis.gtfs.datafetchers.FareProductTypeResolver; +import org.opentripplanner.apis.gtfs.datafetchers.FareProductUseImpl; +import org.opentripplanner.apis.gtfs.datafetchers.FeedImpl; +import org.opentripplanner.apis.gtfs.datafetchers.GeometryImpl; +import org.opentripplanner.apis.gtfs.datafetchers.ItineraryImpl; +import org.opentripplanner.apis.gtfs.datafetchers.LegImpl; +import org.opentripplanner.apis.gtfs.datafetchers.MoneyImpl; +import org.opentripplanner.apis.gtfs.datafetchers.NodeTypeResolver; +import org.opentripplanner.apis.gtfs.datafetchers.OpeningHoursImpl; +import org.opentripplanner.apis.gtfs.datafetchers.PatternImpl; +import org.opentripplanner.apis.gtfs.datafetchers.PlaceImpl; +import org.opentripplanner.apis.gtfs.datafetchers.PlaceInterfaceTypeResolver; +import org.opentripplanner.apis.gtfs.datafetchers.PlanConnectionImpl; +import org.opentripplanner.apis.gtfs.datafetchers.PlanImpl; +import org.opentripplanner.apis.gtfs.datafetchers.QueryTypeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.RentalPlaceTypeResolver; +import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleImpl; +import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleTypeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.RideHailingEstimateImpl; +import org.opentripplanner.apis.gtfs.datafetchers.RouteImpl; +import org.opentripplanner.apis.gtfs.datafetchers.RouteTypeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.RoutingErrorImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StopGeometriesImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StopImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StopOnRouteImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StopOnTripImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StopRelationshipImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StoptimeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StoptimesInPatternImpl; +import org.opentripplanner.apis.gtfs.datafetchers.SystemNoticeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.TicketTypeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.TranslatedStringImpl; +import org.opentripplanner.apis.gtfs.datafetchers.TripImpl; +import org.opentripplanner.apis.gtfs.datafetchers.TripOccupancyImpl; +import org.opentripplanner.apis.gtfs.datafetchers.UnknownImpl; +import org.opentripplanner.apis.gtfs.datafetchers.VehicleParkingImpl; +import org.opentripplanner.apis.gtfs.datafetchers.VehiclePositionImpl; +import org.opentripplanner.apis.gtfs.datafetchers.VehicleRentalNetworkImpl; +import org.opentripplanner.apis.gtfs.datafetchers.VehicleRentalStationImpl; +import org.opentripplanner.apis.gtfs.datafetchers.debugOutputImpl; +import org.opentripplanner.apis.gtfs.datafetchers.elevationProfileComponentImpl; +import org.opentripplanner.apis.gtfs.datafetchers.placeAtDistanceImpl; +import org.opentripplanner.apis.gtfs.datafetchers.serviceTimeRangeImpl; +import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; +import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; +import org.opentripplanner.apis.gtfs.model.StopPosition; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Used to construct {@link GraphQLSchema} instances. + */ +public class SchemaFactory { + + static final Logger LOG = LoggerFactory.getLogger(SchemaFactory.class); + + /** + * @param defaultPreferences used to inject defaults into the schema. Doesn't inject any defaults if preferences are not provided. + */ + @Nullable + public static GraphQLSchema createSchema(@Nullable RoutingPreferences defaultPreferences) { + try { + URL url = Objects.requireNonNull(SchemaFactory.class.getResource("schema.graphqls")); + TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(url.openStream()); + IntrospectionTypeWiring typeWiring = new IntrospectionTypeWiring(typeRegistry); + RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring + .newRuntimeWiring() + .scalar(GraphQLScalars.DURATION_SCALAR) + .scalar(GraphQLScalars.POLYLINE_SCALAR) + .scalar(GraphQLScalars.GEOJSON_SCALAR) + .scalar(GraphQLScalars.GRAPHQL_ID_SCALAR) + .scalar(GraphQLScalars.GRAMS_SCALAR) + .scalar(GraphQLScalars.OFFSET_DATETIME_SCALAR) + .scalar(GraphQLScalars.RATIO_SCALAR) + .scalar(GraphQLScalars.COORDINATE_VALUE_SCALAR) + .scalar(GraphQLScalars.COST_SCALAR) + .scalar(GraphQLScalars.RELUCTANCE_SCALAR) + .scalar(GraphQLScalars.LOCAL_DATE_SCALAR) + .scalar(ExtendedScalars.GraphQLLong) + .scalar(ExtendedScalars.Locale) + .scalar( + ExtendedScalars + .newAliasedScalar("Speed") + .aliasedScalar(ExtendedScalars.NonNegativeFloat) + .build() + ) + .type("Node", type -> type.typeResolver(new NodeTypeResolver())) + .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) + .type("RentalPlace", type -> type.typeResolver(new RentalPlaceTypeResolver())) + .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) + .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) + .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) + .type(typeWiring.build(AgencyImpl.class)) + .type(typeWiring.build(AlertImpl.class)) + .type(typeWiring.build(BikeParkImpl.class)) + .type(typeWiring.build(VehicleParkingImpl.class)) + .type(typeWiring.build(BikeRentalStationImpl.class)) + .type(typeWiring.build(CarParkImpl.class)) + .type(typeWiring.build(CoordinatesImpl.class)) + .type(typeWiring.build(debugOutputImpl.class)) + .type(typeWiring.build(DepartureRowImpl.class)) + .type(typeWiring.build(elevationProfileComponentImpl.class)) + .type(typeWiring.build(FeedImpl.class)) + .type(typeWiring.build(GeometryImpl.class)) + .type(typeWiring.build(ItineraryImpl.class)) + .type(typeWiring.build(LegImpl.class)) + .type(typeWiring.build(PatternImpl.class)) + .type(typeWiring.build(PlaceImpl.class)) + .type(typeWiring.build(placeAtDistanceImpl.class)) + .type(typeWiring.build(PlanConnectionImpl.class)) + .type(typeWiring.build(PlanImpl.class)) + .type(typeWiring.build(QueryTypeImpl.class)) + .type(typeWiring.build(RouteImpl.class)) + .type(typeWiring.build(serviceTimeRangeImpl.class)) + .type(typeWiring.build(stepImpl.class)) + .type(typeWiring.build(StopImpl.class)) + .type(typeWiring.build(stopAtDistanceImpl.class)) + .type(typeWiring.build(StoptimeImpl.class)) + .type(typeWiring.build(StoptimesInPatternImpl.class)) + .type(typeWiring.build(TicketTypeImpl.class)) + .type(typeWiring.build(TranslatedStringImpl.class)) + .type(typeWiring.build(TripImpl.class)) + .type(typeWiring.build(SystemNoticeImpl.class)) + .type(typeWiring.build(ContactInfoImpl.class)) + .type(typeWiring.build(BookingTimeImpl.class)) + .type(typeWiring.build(BookingInfoImpl.class)) + .type(typeWiring.build(VehicleRentalStationImpl.class)) + .type(typeWiring.build(VehicleRentalNetworkImpl.class)) + .type(typeWiring.build(RentalVehicleImpl.class)) + .type(typeWiring.build(RentalVehicleTypeImpl.class)) + .type(typeWiring.build(StopOnRouteImpl.class)) + .type(typeWiring.build(StopOnTripImpl.class)) + .type(typeWiring.build(UnknownImpl.class)) + .type(typeWiring.build(RouteTypeImpl.class)) + .type(typeWiring.build(RoutingErrorImpl.class)) + .type(typeWiring.build(StopGeometriesImpl.class)) + .type(typeWiring.build(VehiclePositionImpl.class)) + .type(typeWiring.build(StopRelationshipImpl.class)) + .type(typeWiring.build(OpeningHoursImpl.class)) + .type(typeWiring.build(RideHailingEstimateImpl.class)) + .type(typeWiring.build(MoneyImpl.class)) + .type(typeWiring.build(CurrencyImpl.class)) + .type(typeWiring.build(FareProductUseImpl.class)) + .type(typeWiring.build(DefaultFareProductImpl.class)) + .type(typeWiring.build(TripOccupancyImpl.class)); + + if (defaultPreferences != null) { + runtimeWiringBuilder.directiveWiring(new DefaultValueDirectiveWiring(defaultPreferences)); + } + + SchemaGenerator schemaGenerator = new SchemaGenerator(); + return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiringBuilder.build()); + } catch (Exception e) { + LOG.error("Unable to build GTFS GraphQL Schema", e); + } + return null; + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java b/application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java new file mode 100644 index 00000000000..ba6f2375567 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java @@ -0,0 +1,22 @@ +package org.opentripplanner.apis.gtfs.service; + +import graphql.schema.GraphQLSchema; +import jakarta.inject.Inject; + +/** + * Service for fetching the {@link GraphQLSchema}. The purpose of this class is to avoid + * reconstructing the schema on each request. + */ +public class SchemaService { + + private final GraphQLSchema schema; + + @Inject + public SchemaService(GraphQLSchema schema) { + this.schema = schema; + } + + public GraphQLSchema schema() { + return schema; + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java new file mode 100644 index 00000000000..5ce5760d290 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java @@ -0,0 +1,23 @@ +package org.opentripplanner.apis.gtfs.service.configure; + +import dagger.Module; +import dagger.Provides; +import jakarta.inject.Singleton; +import org.opentripplanner.apis.gtfs.SchemaFactory; +import org.opentripplanner.apis.gtfs.service.SchemaService; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; + +/** + * The service is used during application serve phase, not loading, and it depends on the default + * routing preferences, which are injected from the + * {@link org.opentripplanner.standalone.config.RouterConfig}. + */ +@Module +public class SchemaServiceModule { + + @Provides + @Singleton + public SchemaService provideSchemaService(RoutingPreferences defaultPreferences) { + return new SchemaService(SchemaFactory.createSchema(defaultPreferences)); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index f088a3de60e..c2bae94c627 100644 --- a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Locale; import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; import org.opentripplanner.ext.emissions.EmissionsService; @@ -153,4 +154,7 @@ default DataOverlayContext dataOverlayContext(RouteRequest request) { @Nullable SorlandsbanenNorwayService sorlandsbanenService(); + + @Nullable + SchemaService schemaService(); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index b4edbb36299..e75d791d9b6 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -102,6 +102,7 @@ public class ConstructApplication { .dataImportIssueSummary(issueSummary) .stopConsolidationRepository(stopConsolidationRepository) .streetLimitationParameters(streetLimitationParameters) + .schemaService(config.routerConfig().routingRequestDefaults().preferences()) .build(); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index d6310c0c616..4785ec3050a 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -4,6 +4,8 @@ import dagger.Component; import jakarta.inject.Singleton; import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.service.SchemaService; +import org.opentripplanner.apis.gtfs.service.configure.SchemaServiceModule; import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.emissions.EmissionsServiceModule; import org.opentripplanner.ext.geocoder.LuceneIndex; @@ -17,6 +19,7 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; @@ -62,6 +65,7 @@ RideHailingServicesModule.class, EmissionsServiceModule.class, SorlandsbanenNorwayModule.class, + SchemaServiceModule.class, StopConsolidationServiceModule.class, InteractiveLauncherModule.class, StreetLimitationParametersServiceModule.class, @@ -102,6 +106,9 @@ public interface ConstructApplicationFactory { @Nullable SorlandsbanenNorwayService enturSorlandsbanenService(); + @Nullable + SchemaService schemaService(); + @Nullable LuceneIndex luceneIndex(); @@ -136,6 +143,9 @@ Builder stopConsolidationRepository( @BindsInstance Builder emissionsDataModel(EmissionsDataModel emissionsDataModel); + @BindsInstance + Builder schemaService(RoutingPreferences defaultRoutingPreferences); + @BindsInstance Builder streetLimitationParameters(StreetLimitationParameters streetLimitationParameters); diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index 42bd8ee4d87..e9ddeb51356 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -5,6 +5,7 @@ import io.micrometer.core.instrument.Metrics; import java.util.List; import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.geocoder.LuceneIndex; @@ -47,6 +48,7 @@ OtpServerRequestContext providesServerContext( @Nullable TraverseVisitor traverseVisitor, EmissionsService emissionsService, @Nullable SorlandsbanenNorwayService sorlandsbanenService, + @Nullable SchemaService schemaService, LauncherRequestDecorator launcherRequestDecorator, @Nullable LuceneIndex luceneIndex ) { @@ -66,6 +68,7 @@ OtpServerRequestContext providesServerContext( vehicleParkingService, emissionsService, sorlandsbanenService, + schemaService, routerConfig.flexParameters(), rideHailingServices, stopConsolidationService, diff --git a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 450b6986cb5..1e75998fdf2 100644 --- a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Locale; import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.flex.FlexParameters; @@ -52,6 +53,9 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final VehicleParkingService vehicleParkingService; private final EmissionsService emissionsService; + @Nullable + private final SchemaService schemaService; + @Nullable private final SorlandsbanenNorwayService sorlandsbanenService; @@ -80,6 +84,7 @@ private DefaultServerRequestContext( VehicleParkingService vehicleParkingService, @Nullable EmissionsService emissionsService, @Nullable SorlandsbanenNorwayService sorlandsbanenService, + @Nullable SchemaService schemaService, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, StreetLimitationParametersService streetLimitationParametersService, @@ -105,6 +110,7 @@ private DefaultServerRequestContext( this.rideHailingServices = rideHailingServices; this.emissionsService = emissionsService; this.sorlandsbanenService = sorlandsbanenService; + this.schemaService = schemaService; this.stopConsolidationService = stopConsolidationService; this.streetLimitationParametersService = streetLimitationParametersService; this.luceneIndex = luceneIndex; @@ -128,6 +134,7 @@ public static DefaultServerRequestContext create( VehicleParkingService vehicleParkingService, @Nullable EmissionsService emissionsService, @Nullable SorlandsbanenNorwayService sorlandsbanenService, + @Nullable SchemaService schemaService, FlexParameters flexParameters, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, @@ -151,6 +158,7 @@ public static DefaultServerRequestContext create( vehicleParkingService, emissionsService, sorlandsbanenService, + schemaService, rideHailingServices, stopConsolidationService, streetLimitationParametersService, @@ -233,6 +241,12 @@ public List rideHailingServices() { return rideHailingServices; } + @Nullable + @Override + public SchemaService schemaService() { + return schemaService; + } + @Override public StopConsolidationService stopConsolidationService() { return stopConsolidationService; diff --git a/application/src/test/java/org/opentripplanner/TestServerContext.java b/application/src/test/java/org/opentripplanner/TestServerContext.java index ca818a64a58..3515a234c38 100644 --- a/application/src/test/java/org/opentripplanner/TestServerContext.java +++ b/application/src/test/java/org/opentripplanner/TestServerContext.java @@ -61,6 +61,7 @@ public static OtpServerRequestContext createServerContext( createVehicleParkingService(), createEmissionsService(), null, + null, routerConfig.flexParameters(), List.of(), null, diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java index 1dcae08953f..edd15d61ba2 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java @@ -17,7 +17,7 @@ public class GraphQLFormattingTest { @Test public void format() { String original = readFile(SCHEMA_FILE); - var schema = GtfsGraphQLIndex.buildSchema(null); + var schema = SchemaFactory.createSchema(null); writeFile(SCHEMA_FILE, new SchemaPrinter().print(schema)); assertFileEquals(original, SCHEMA_FILE); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java index a925bc749cc..e5a5847d5ea 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java @@ -20,7 +20,7 @@ public class GraphQLIndexTest { @Test public void build() { - var schema = GtfsGraphQLIndex.buildSchema(new RoutingPreferences()); + var schema = SchemaFactory.createSchema(new RoutingPreferences()); assertNotNull(schema); } @@ -28,12 +28,12 @@ public void build() { @ParameterizedTest(name = "\"{0}\" must be a an async fetcher") void asyncDataFetchers(String fieldName) { OTPFeature.AsyncGraphQLFetchers.testOn(() -> { - var schema = GtfsGraphQLIndex.buildSchema(new RoutingPreferences()); + var schema = SchemaFactory.createSchema(new RoutingPreferences()); var fetcher = getQueryType(fieldName, schema); assertSame(fetcher.getClass(), AsyncDataFetcher.class); }); OTPFeature.AsyncGraphQLFetchers.testOff(() -> { - var schema = GtfsGraphQLIndex.buildSchema(new RoutingPreferences()); + var schema = SchemaFactory.createSchema(new RoutingPreferences()); var fetcher = getQueryType(fieldName, schema); assertNotSame(fetcher.getClass(), AsyncDataFetcher.class); }); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 12e68c2d453..92c0391ff52 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -36,6 +36,7 @@ import org.locationtech.jts.geom.Coordinate; import org.opentripplanner._support.text.I18NStrings; import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.ext.fares.FaresToItineraryMapper; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.framework.geometry.WgsCoordinate; @@ -312,6 +313,7 @@ public Set findRoutes(StopLocation stop) { defaultVehicleRentalService.addVehicleRentalStation(VEHICLE_RENTAL_STATION); defaultVehicleRentalService.addVehicleRentalStation(RENTAL_VEHICLE); + var routeRequest = new RouteRequest(); context = new GraphQLRequestContext( new TestRoutingService(List.of(i1)), @@ -320,8 +322,9 @@ public Set findRoutes(StopLocation stop) { defaultVehicleRentalService, new DefaultVehicleParkingService(parkingRepository), realtimeVehicleService, + new SchemaService(SchemaFactory.createSchema(routeRequest.preferences())), finder, - new RouteRequest() + routeRequest ); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 43dd10dbdce..ff18d0d5007 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -24,8 +24,10 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.SchemaFactory; import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.api.request.RouteRequest; @@ -57,6 +59,7 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { var timetableRepository = new TimetableRepository(stopModelBuilder.build(), new Deduplicator()); timetableRepository.initTimeZone(ZoneIds.BERLIN); final DefaultTransitService transitService = new DefaultTransitService(timetableRepository); + var routeRequest = new RouteRequest(); context = new GraphQLRequestContext( new TestRoutingService(List.of()), @@ -65,8 +68,9 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), + new SchemaService(SchemaFactory.createSchema(routeRequest.preferences())), GraphFinder.getInstance(graph, transitService::findRegularStops), - new RouteRequest() + routeRequest ); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 34cb865c81a..997753882bd 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -23,7 +23,9 @@ import org.locationtech.jts.geom.Coordinate; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.SchemaFactory; import org.opentripplanner.apis.gtfs.TestRoutingService; +import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.request.RouteRequest; @@ -67,6 +69,7 @@ class RouteRequestMapperTest { var timetableRepository = new TimetableRepository(); timetableRepository.initTimeZone(ZoneIds.BERLIN); final DefaultTransitService transitService = new DefaultTransitService(timetableRepository); + var routeRequest = new RouteRequest(); CONTEXT = new GraphQLRequestContext( new TestRoutingService(List.of()), @@ -75,8 +78,9 @@ class RouteRequestMapperTest { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), + new SchemaService(SchemaFactory.createSchema(routeRequest.preferences())), GraphFinder.getInstance(graph, transitService::findRegularStops), - new RouteRequest() + routeRequest ); } diff --git a/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index dc96a094812..8a317e694dc 100644 --- a/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -150,6 +150,7 @@ void setup() { new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultEmissionsService(new EmissionsDataModel()), null, + null, RouterConfig.DEFAULT.flexParameters(), List.of(), null, diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index ca4e85eed84..5b628c3033e 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -126,6 +126,7 @@ public SpeedTest( TestServerContext.createVehicleParkingService(), TestServerContext.createEmissionsService(), null, + null, config.flexConfig, List.of(), null, From f639ce5dcebfdb2e36fc81df87d7425fbb32f201 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 30 Dec 2024 17:31:00 +0200 Subject: [PATCH 04/22] Only construct schema if feature flag is on --- .../apis/gtfs/service/configure/SchemaServiceModule.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java index 5ce5760d290..0d49cafe21d 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java @@ -5,12 +5,14 @@ import jakarta.inject.Singleton; import org.opentripplanner.apis.gtfs.SchemaFactory; import org.opentripplanner.apis.gtfs.service.SchemaService; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; /** * The service is used during application serve phase, not loading, and it depends on the default * routing preferences, which are injected from the - * {@link org.opentripplanner.standalone.config.RouterConfig}. + * {@link org.opentripplanner.standalone.config.RouterConfig}. The {@link SchemaService} is only + * constructed if the API feature flag is on. */ @Module public class SchemaServiceModule { @@ -18,6 +20,8 @@ public class SchemaServiceModule { @Provides @Singleton public SchemaService provideSchemaService(RoutingPreferences defaultPreferences) { - return new SchemaService(SchemaFactory.createSchema(defaultPreferences)); + return OTPFeature.GtfsGraphQlApi.isOn() + ? new SchemaService(SchemaFactory.createSchema(defaultPreferences)) + : null; } } From 38b5a5bc2b0e3fcf6618803286c736864f3d4c3f Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 30 Dec 2024 17:38:06 +0200 Subject: [PATCH 05/22] Use a slightly prettier method for getting parent name --- .../opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java index 359fbc8aaf4..5be36c6f1ab 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java @@ -20,7 +20,8 @@ public GraphQLInputObjectField onInputObjectField( SchemaDirectiveWiringEnvironment environment ) { GraphQLInputObjectField field = environment.getElement(); - var parentName = environment.getNodeParentTree().toList().get(1).getName(); + // Input object fields always have a parent + var parentName = environment.getNodeParentTree().getParentInfo().get().getNode().getName(); var key = parentName + "_" + field.getName(); var defaultValue = getDefaultValueForKey(key); if (defaultValue != null) { From 698349ee20c2433366a076dc9728d8b559305ca2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 13:37:53 +0200 Subject: [PATCH 06/22] Replace preferences with route request We need some defaults from the request --- .../apis/gtfs/DefaultValueDirectiveWiring.java | 11 ++++++----- .../org/opentripplanner/apis/gtfs/SchemaFactory.java | 10 +++++----- .../gtfs/service/configure/SchemaServiceModule.java | 8 ++++---- .../standalone/configure/ConstructApplication.java | 2 +- .../configure/ConstructApplicationFactory.java | 4 ++-- .../opentripplanner/apis/gtfs/GraphQLIndexTest.java | 8 ++++---- .../apis/gtfs/GraphQLIntegrationTest.java | 2 +- .../routerequest/LegacyRouteRequestMapperTest.java | 2 +- .../mapping/routerequest/RouteRequestMapperTest.java | 2 +- 9 files changed, 25 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java index 5be36c6f1ab..8bcbc954253 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java @@ -5,14 +5,14 @@ import graphql.schema.GraphQLInputObjectField; import graphql.schema.idl.SchemaDirectiveWiring; import graphql.schema.idl.SchemaDirectiveWiringEnvironment; -import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.RouteRequest; public class DefaultValueDirectiveWiring implements SchemaDirectiveWiring { - private final RoutingPreferences defaultPreferences; + private final RouteRequest defaultRouteRequest; - public DefaultValueDirectiveWiring(RoutingPreferences defaultPreferences) { - this.defaultPreferences = defaultPreferences; + public DefaultValueDirectiveWiring(RouteRequest defaultRouteRequest) { + this.defaultRouteRequest = defaultRouteRequest; } @Override @@ -31,9 +31,10 @@ public GraphQLInputObjectField onInputObjectField( } private Value getDefaultValueForKey(String key) { + var preferences = defaultRouteRequest.preferences(); switch (key) { case "BicyclePreferencesInput_boardCost": - return IntValue.of(defaultPreferences.bike().boardCost()); + return IntValue.of(preferences.bike().boardCost()); default: return null; } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java index 30f00f55e2c..98a256dda34 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -68,7 +68,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; import org.opentripplanner.apis.gtfs.model.StopPosition; -import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.RouteRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,10 +80,10 @@ public class SchemaFactory { static final Logger LOG = LoggerFactory.getLogger(SchemaFactory.class); /** - * @param defaultPreferences used to inject defaults into the schema. Doesn't inject any defaults if preferences are not provided. + * @param defaultRouteRequest used to inject defaults into the schema. Doesn't inject any defaults if preferences are not provided. */ @Nullable - public static GraphQLSchema createSchema(@Nullable RoutingPreferences defaultPreferences) { + public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequest) { try { URL url = Objects.requireNonNull(SchemaFactory.class.getResource("schema.graphqls")); TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(url.openStream()); @@ -169,8 +169,8 @@ public static GraphQLSchema createSchema(@Nullable RoutingPreferences defaultPre .type(typeWiring.build(DefaultFareProductImpl.class)) .type(typeWiring.build(TripOccupancyImpl.class)); - if (defaultPreferences != null) { - runtimeWiringBuilder.directiveWiring(new DefaultValueDirectiveWiring(defaultPreferences)); + if (defaultRouteRequest != null) { + runtimeWiringBuilder.directiveWiring(new DefaultValueDirectiveWiring(defaultRouteRequest)); } SchemaGenerator schemaGenerator = new SchemaGenerator(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java index 0d49cafe21d..1e060b205b5 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java @@ -6,11 +6,11 @@ import org.opentripplanner.apis.gtfs.SchemaFactory; import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.RouteRequest; /** * The service is used during application serve phase, not loading, and it depends on the default - * routing preferences, which are injected from the + * route request, which is injected from the * {@link org.opentripplanner.standalone.config.RouterConfig}. The {@link SchemaService} is only * constructed if the API feature flag is on. */ @@ -19,9 +19,9 @@ public class SchemaServiceModule { @Provides @Singleton - public SchemaService provideSchemaService(RoutingPreferences defaultPreferences) { + public SchemaService provideSchemaService(RouteRequest defaultRouteRequest) { return OTPFeature.GtfsGraphQlApi.isOn() - ? new SchemaService(SchemaFactory.createSchema(defaultPreferences)) + ? new SchemaService(SchemaFactory.createSchema(defaultRouteRequest)) : null; } } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index e75d791d9b6..13105e4f898 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -102,7 +102,7 @@ public class ConstructApplication { .dataImportIssueSummary(issueSummary) .stopConsolidationRepository(stopConsolidationRepository) .streetLimitationParameters(streetLimitationParameters) - .schemaService(config.routerConfig().routingRequestDefaults().preferences()) + .schemaService(config.routerConfig().routingRequestDefaults()) .build(); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 4785ec3050a..c0ae414e839 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -19,7 +19,7 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; -import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; @@ -144,7 +144,7 @@ Builder stopConsolidationRepository( Builder emissionsDataModel(EmissionsDataModel emissionsDataModel); @BindsInstance - Builder schemaService(RoutingPreferences defaultRoutingPreferences); + Builder schemaService(RouteRequest defaultRouteRequest); @BindsInstance Builder streetLimitationParameters(StreetLimitationParameters streetLimitationParameters); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java index e5a5847d5ea..40cc7f569e2 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java @@ -14,13 +14,13 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.RouteRequest; public class GraphQLIndexTest { @Test public void build() { - var schema = SchemaFactory.createSchema(new RoutingPreferences()); + var schema = SchemaFactory.createSchema(new RouteRequest()); assertNotNull(schema); } @@ -28,12 +28,12 @@ public void build() { @ParameterizedTest(name = "\"{0}\" must be a an async fetcher") void asyncDataFetchers(String fieldName) { OTPFeature.AsyncGraphQLFetchers.testOn(() -> { - var schema = SchemaFactory.createSchema(new RoutingPreferences()); + var schema = SchemaFactory.createSchema(new RouteRequest()); var fetcher = getQueryType(fieldName, schema); assertSame(fetcher.getClass(), AsyncDataFetcher.class); }); OTPFeature.AsyncGraphQLFetchers.testOff(() -> { - var schema = SchemaFactory.createSchema(new RoutingPreferences()); + var schema = SchemaFactory.createSchema(new RouteRequest()); var fetcher = getQueryType(fieldName, schema); assertNotSame(fetcher.getClass(), AsyncDataFetcher.class); }); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 92c0391ff52..df4ec5223cf 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -322,7 +322,7 @@ public Set findRoutes(StopLocation stop) { defaultVehicleRentalService, new DefaultVehicleParkingService(parkingRepository), realtimeVehicleService, - new SchemaService(SchemaFactory.createSchema(routeRequest.preferences())), + new SchemaService(SchemaFactory.createSchema(routeRequest)), finder, routeRequest ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index ff18d0d5007..84e3e709b67 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -68,7 +68,7 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - new SchemaService(SchemaFactory.createSchema(routeRequest.preferences())), + new SchemaService(SchemaFactory.createSchema(routeRequest)), GraphFinder.getInstance(graph, transitService::findRegularStops), routeRequest ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 997753882bd..561ac68b8e7 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -78,7 +78,7 @@ class RouteRequestMapperTest { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - new SchemaService(SchemaFactory.createSchema(routeRequest.preferences())), + new SchemaService(SchemaFactory.createSchema(routeRequest)), GraphFinder.getInstance(graph, transitService::findRegularStops), routeRequest ); From 7162c7a319b376502d279717aef12e7157b0d8ed Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 13:48:35 +0200 Subject: [PATCH 07/22] Add structure for injecting defaults for arguments and use it for --- .../gtfs/DefaultValueDirectiveWiring.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java index 8bcbc954253..fa6cfce2714 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java @@ -2,6 +2,7 @@ import graphql.language.IntValue; import graphql.language.Value; +import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLInputObjectField; import graphql.schema.idl.SchemaDirectiveWiring; import graphql.schema.idl.SchemaDirectiveWiringEnvironment; @@ -15,24 +16,36 @@ public DefaultValueDirectiveWiring(RouteRequest defaultRouteRequest) { this.defaultRouteRequest = defaultRouteRequest; } + @Override + public GraphQLArgument onArgument(SchemaDirectiveWiringEnvironment environment) { + GraphQLArgument argument = environment.getElement(); + var defaultValue = getDefaultValueForSchemaObject(environment); + if (defaultValue != null) { + return argument.transform(builder -> builder.defaultValueLiteral(defaultValue).build()); + } + return argument; + } + @Override public GraphQLInputObjectField onInputObjectField( SchemaDirectiveWiringEnvironment environment ) { GraphQLInputObjectField field = environment.getElement(); - // Input object fields always have a parent - var parentName = environment.getNodeParentTree().getParentInfo().get().getNode().getName(); - var key = parentName + "_" + field.getName(); - var defaultValue = getDefaultValueForKey(key); + var defaultValue = getDefaultValueForSchemaObject(environment); if (defaultValue != null) { return field.transform(builder -> builder.defaultValueLiteral(defaultValue).build()); } return field; } - private Value getDefaultValueForKey(String key) { + private Value getDefaultValueForSchemaObject(SchemaDirectiveWiringEnvironment environment) { + // Arguments and input fields always have a parent + var parentName = environment.getNodeParentTree().getParentInfo().get().getNode().getName(); + var key = parentName + "_" + environment.getElement().getName(); var preferences = defaultRouteRequest.preferences(); switch (key) { + case "planConnection_first": + return IntValue.of(defaultRouteRequest.numItineraries()); case "BicyclePreferencesInput_boardCost": return IntValue.of(preferences.bike().boardCost()); default: From 280fd4531685eb7bbd24785c6586b4ae4a8b8fd9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 14:44:44 +0200 Subject: [PATCH 08/22] Add default for search window and allow resetting it to null --- .../apis/gtfs/DefaultValueDirectiveWiring.java | 5 +++++ .../gtfs/mapping/routerequest/RouteRequestMapper.java | 10 +++++----- .../routing/api/request/RouteRequest.java | 2 +- .../org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java index fa6cfce2714..a22b4b151ea 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java @@ -1,6 +1,7 @@ package org.opentripplanner.apis.gtfs; import graphql.language.IntValue; +import graphql.language.StringValue; import graphql.language.Value; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLInputObjectField; @@ -46,6 +47,10 @@ private Value getDefaultValueForSchemaObject(SchemaDirectiveWiringEnvironment switch (key) { case "planConnection_first": return IntValue.of(defaultRouteRequest.numItineraries()); + case "planConnection_searchWindow": + return defaultRouteRequest.searchWindow() != null + ? StringValue.of(defaultRouteRequest.searchWindow().toString()) + : null; case "BicyclePreferencesInput_boardCost": return IntValue.of(preferences.bike().boardCost()); default: diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index 23e42f9a70c..718005f4b76 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -41,11 +41,11 @@ public static RouteRequest toRouteRequest( request.setFrom(parseGenericLocation(args.getGraphQLOrigin())); request.setTo(parseGenericLocation(args.getGraphQLDestination())); request.setLocale(GraphQLUtils.getLocale(environment, args.getGraphQLLocale())); - if (args.getGraphQLSearchWindow() != null) { - request.setSearchWindow( - DurationUtils.requireNonNegativeMax2days(args.getGraphQLSearchWindow(), "searchWindow") - ); - } + request.setSearchWindow( + args.getGraphQLSearchWindow() != null + ? DurationUtils.requireNonNegativeMax2days(args.getGraphQLSearchWindow(), "searchWindow") + : null + ); if (args.getGraphQLBefore() != null) { request.setPageCursorFromEncoded(args.getGraphQLBefore()); diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 4e36b1b58db..daafe70f9e7 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -320,7 +320,7 @@ public Duration searchWindow() { return searchWindow; } - public void setSearchWindow(Duration searchWindow) { + public void setSearchWindow(@Nullable Duration searchWindow) { if (searchWindow != null) { if (hasMaxSearchWindow() && searchWindow.toSeconds() > maxSearchWindow.toSeconds()) { throw new IllegalArgumentException("The search window cannot exceed " + maxSearchWindow); diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index f92a8191018..760cb4ea89f 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1636,8 +1636,8 @@ type QueryType { preferences: PlanPreferencesInput, """ Duration of the search window. This either starts at the defined earliest departure - time or ends at the latest arrival time. If this is not provided, a reasonable - search window is automatically generated. When searching for earlier or later itineraries + time or ends at the latest arrival time. If this is not provided or the value is set as null, + a reasonable search window is automatically generated. When searching for earlier or later itineraries with paging, this search window is no longer used and the new window will be based on how many suggestions were returned in the previous search. The new search window can be shorter or longer than the original search window. Note, itineraries are returned faster From 45eea03021e25f6dc25b6509a76231dbec8a0987 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 15:46:19 +0200 Subject: [PATCH 09/22] Add the rest of defaults --- .../gtfs/DefaultValueDirectiveWiring.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java index a22b4b151ea..bbd59219a2c 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java @@ -1,5 +1,7 @@ package org.opentripplanner.apis.gtfs; +import graphql.language.BooleanValue; +import graphql.language.FloatValue; import graphql.language.IntValue; import graphql.language.StringValue; import graphql.language.Value; @@ -51,8 +53,81 @@ private Value getDefaultValueForSchemaObject(SchemaDirectiveWiringEnvironment return defaultRouteRequest.searchWindow() != null ? StringValue.of(defaultRouteRequest.searchWindow().toString()) : null; + case "AlightPreferencesInput_slack": + return StringValue.of(preferences.transit().alightSlack().defaultValue().toString()); + case "BicycleParkingPreferencesInput_unpreferredCost": + return IntValue.of( + preferences.bike().parking().unpreferredVehicleParkingTagCost().toSeconds() + ); case "BicyclePreferencesInput_boardCost": return IntValue.of(preferences.bike().boardCost()); + case "BicyclePreferencesInput_reluctance": + return FloatValue.of(preferences.bike().reluctance()); + case "BicyclePreferencesInput_speed": + return FloatValue.of(preferences.bike().speed()); + case "BicycleWalkPreferencesCostInput_mountDismountCost": + return IntValue.of(preferences.bike().walking().mountDismountCost().toSeconds()); + case "BicycleWalkPreferencesCostInput_reluctance": + return FloatValue.of(preferences.bike().walking().reluctance()); + case "BicycleWalkPreferencesInput_mountDismountTime": + return StringValue.of(preferences.bike().walking().mountDismountTime().toString()); + case "BicycleWalkPreferencesInput_speed": + return FloatValue.of(preferences.bike().walking().speed()); + case "BoardPreferencesInput_slack": + return StringValue.of(preferences.transit().boardSlack().defaultValue().toString()); + case "BoardPreferencesInput_waitReluctance": + return FloatValue.of(preferences.transfer().waitReluctance()); + case "CarParkingPreferencesInput_unpreferredCost": + return IntValue.of( + preferences.car().parking().unpreferredVehicleParkingTagCost().toSeconds() + ); + case "CarPreferencesInput_reluctance": + return FloatValue.of(preferences.car().reluctance()); + case "DestinationBicyclePolicyInput_allowKeeping": + return BooleanValue.of( + preferences.bike().rental().allowArrivingInRentedVehicleAtDestination() + ); + case "DestinationBicyclePolicyInput_keepingCost": + return IntValue.of( + preferences.bike().rental().arrivingInRentalVehicleAtDestinationCost().toSeconds() + ); + case "DestinationScooterPolicyInput_allowKeeping": + return BooleanValue.of( + preferences.scooter().rental().allowArrivingInRentedVehicleAtDestination() + ); + case "DestinationScooterPolicyInput_keepingCost": + return IntValue.of( + preferences.scooter().rental().arrivingInRentalVehicleAtDestinationCost().toSeconds() + ); + case "ScooterPreferencesInput_reluctance": + return FloatValue.of(preferences.scooter().reluctance()); + case "ScooterPreferencesInput_speed": + return FloatValue.of(preferences.scooter().speed()); + case "TimetablePreferencesInput_excludeRealTimeUpdates": + return BooleanValue.of(preferences.transit().ignoreRealtimeUpdates()); + case "TimetablePreferencesInput_includePlannedCancellations": + return BooleanValue.of(preferences.transit().includePlannedCancellations()); + case "TimetablePreferencesInput_includeRealTimeCancellations": + return BooleanValue.of(preferences.transit().includeRealtimeCancellations()); + case "TransferPreferencesInput_cost": + return IntValue.of(preferences.transfer().cost()); + case "TransferPreferencesInput_maximumAdditionalTransfers": + return IntValue.of(preferences.transfer().maxAdditionalTransfers()); + case "TransferPreferencesInput_maximumTransfers": + // Max transfers are wrong in the internal model but fixed in the API mapping + return IntValue.of(preferences.transfer().maxTransfers() - 1); + case "TransferPreferencesInput_slack": + return StringValue.of(preferences.transfer().slack().toString()); + case "WalkPreferencesInput_boardCost": + return IntValue.of(preferences.walk().boardCost()); + case "WalkPreferencesInput_reluctance": + return FloatValue.of(preferences.walk().reluctance()); + case "WalkPreferencesInput_safetyFactor": + return FloatValue.of(preferences.walk().safetyFactor()); + case "WalkPreferencesInput_speed": + return FloatValue.of(preferences.walk().speed()); + case "WheelchairPreferencesInput_enabled": + return BooleanValue.of(defaultRouteRequest.wheelchair()); default: return null; } From d298811012692b3585a953582af6eb89adabf01e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 15:46:34 +0200 Subject: [PATCH 10/22] Fix schema comment about duration being seconds --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 760cb4ea89f..76c4db725da 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4372,11 +4372,11 @@ input TransferPreferencesInput { "How many transfers there can be at maximum in an itinerary." maximumTransfers: Int """ - A global minimum transfer time (in seconds) that specifies the minimum amount of time - that must pass between exiting one transit vehicle and boarding another. This time is - in addition to time it might take to walk between transit stops. Setting this value - as `PT0S`, for example, can lead to passenger missing a connection when the vehicle leaves - ahead of time or the passenger arrives to the stop later than expected. + A global minimum transfer time that specifies the minimum amount of time that must pass + between exiting one transit vehicle and boarding another. This time is in addition to + time it might take to walk between transit stops. Setting this value as `PT0S`, for + example, can lead to passenger missing a connection when the vehicle leaves ahead of time + or the passenger arrives to the stop later than expected. """ slack: Duration } From 5fe8da84d0fe1a4d70034397efeee2a6faab0b82 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 15:50:57 +0200 Subject: [PATCH 11/22] Move schema tests to new file --- .../gtfs/{GraphQLIndexTest.java => SchemaFactoryTest.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename application/src/test/java/org/opentripplanner/apis/gtfs/{GraphQLIndexTest.java => SchemaFactoryTest.java} (96%) diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java similarity index 96% rename from application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java rename to application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java index 40cc7f569e2..69122d9459d 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIndexTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java @@ -16,10 +16,10 @@ import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.api.request.RouteRequest; -public class GraphQLIndexTest { +public class SchemaFactoryTest { @Test - public void build() { + public void createSchema() { var schema = SchemaFactory.createSchema(new RouteRequest()); assertNotNull(schema); } From 4f89618c1ffd1403133613d408d65fd85461dee7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 16:23:12 +0200 Subject: [PATCH 12/22] Add tests for default injection --- .../apis/gtfs/SchemaFactoryTest.java | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java index 69122d9459d..547ec1ff86b 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java @@ -1,13 +1,18 @@ package org.opentripplanner.apis.gtfs; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.Value; import graphql.schema.AsyncDataFetcher; import graphql.schema.DataFetcher; import graphql.schema.FieldCoordinates; import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import org.junit.jupiter.api.Test; @@ -19,11 +24,49 @@ public class SchemaFactoryTest { @Test - public void createSchema() { + void createSchema() { var schema = SchemaFactory.createSchema(new RouteRequest()); assertNotNull(schema); } + @Test + void testDefaultValueInjection() { + var routeRequest = new RouteRequest(); + double walkSpeed = 15; + routeRequest.withPreferences(preferences -> + preferences.withWalk(walk -> walk.withSpeed(walkSpeed)) + ); + var maxTransfers = 2; + routeRequest.withPreferences(preferences -> + preferences.withTransfer(transfer -> transfer.withMaxTransfers(maxTransfers + 1)) + ); + var numItineraries = 63; + routeRequest.setNumItineraries(numItineraries); + var schema = SchemaFactory.createSchema(routeRequest); + assertNotNull(schema); + + var defaultSpeed = (FloatValue) getDefaultValueForField( + schema, + "WalkPreferencesInput", + "speed" + ); + assertEquals(walkSpeed, defaultSpeed.getValue().doubleValue(), 0.01f); + + var defaultMaxTransfers = (IntValue) getDefaultValueForField( + schema, + "TransferPreferencesInput", + "maximumTransfers" + ); + assertEquals(maxTransfers, defaultMaxTransfers.getValue().intValue()); + + var defaultNumberOfItineraries = (IntValue) getDefaultValueForArgument( + schema, + "planConnection", + "first" + ); + assertEquals(numItineraries, defaultNumberOfItineraries.getValue().intValue()); + } + @ValueSource(strings = { "plan", "nearest" }) @ParameterizedTest(name = "\"{0}\" must be a an async fetcher") void asyncDataFetchers(String fieldName) { @@ -51,4 +94,22 @@ private static DataFetcher getQueryType(String fieldName, GraphQLSchema schem .build() ); } + + private static Value getDefaultValueForField( + GraphQLSchema schema, + String inputObjectName, + String fieldName + ) { + GraphQLInputObjectType inputObject = schema.getTypeAs(inputObjectName); + return (Value) inputObject.getField(fieldName).getInputFieldDefaultValue().getValue(); + } + + private static Value getDefaultValueForArgument( + GraphQLSchema schema, + String queryName, + String argumentName + ) { + var query = schema.getQueryType().getField(queryName); + return (Value) query.getArgument(argumentName).getArgumentDefaultValue().getValue(); + } } From c105fc2e68770875ff9c44ca47c8aab782aa4e41 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Dec 2024 16:56:42 +0200 Subject: [PATCH 13/22] Fix comment --- .../main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java index 9fd19acf2ae..eca8b1e48c7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -87,7 +87,8 @@ public class SchemaFactory { static final Logger LOG = LoggerFactory.getLogger(SchemaFactory.class); /** - * @param defaultRouteRequest used to inject defaults into the schema. Doesn't inject any defaults if preferences are not provided. + * @param defaultRouteRequest used to inject defaults into the schema. Doesn't inject any defaults + * if the request is not provided. */ @Nullable public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequest) { From 3beb9be284a1c733901bab0a041c6fb370d2c8c2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 3 Jan 2025 16:33:46 +0200 Subject: [PATCH 14/22] Use schema visitor instead of directive wiring --- ...eWiring.java => DefaultValueInjector.java} | 52 ++++++++++++------- .../apis/gtfs/SchemaFactory.java | 15 +++--- 2 files changed, 42 insertions(+), 25 deletions(-) rename application/src/main/java/org/opentripplanner/apis/gtfs/{DefaultValueDirectiveWiring.java => DefaultValueInjector.java} (78%) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java similarity index 78% rename from application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java rename to application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java index bbd59219a2c..7c148d6092a 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueDirectiveWiring.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java @@ -7,44 +7,60 @@ import graphql.language.Value; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLInputObjectField; -import graphql.schema.idl.SchemaDirectiveWiring; -import graphql.schema.idl.SchemaDirectiveWiringEnvironment; +import graphql.schema.GraphQLNamedSchemaElement; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.GraphQLTypeVisitorStub; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import org.opentripplanner.routing.api.request.RouteRequest; -public class DefaultValueDirectiveWiring implements SchemaDirectiveWiring { +public class DefaultValueInjector extends GraphQLTypeVisitorStub implements GraphQLTypeVisitor { private final RouteRequest defaultRouteRequest; - public DefaultValueDirectiveWiring(RouteRequest defaultRouteRequest) { + public DefaultValueInjector(RouteRequest defaultRouteRequest) { this.defaultRouteRequest = defaultRouteRequest; } @Override - public GraphQLArgument onArgument(SchemaDirectiveWiringEnvironment environment) { - GraphQLArgument argument = environment.getElement(); - var defaultValue = getDefaultValueForSchemaObject(environment); + public TraversalControl visitGraphQLArgument( + GraphQLArgument argument, + TraverserContext context + ) { + var defaultValue = getDefaultValueForSchemaObject(context, argument.getName()); if (defaultValue != null) { - return argument.transform(builder -> builder.defaultValueLiteral(defaultValue).build()); + return changeNode( + context, + argument.transform(builder -> builder.defaultValueLiteral(defaultValue).build()) + ); } - return argument; + return TraversalControl.CONTINUE; } @Override - public GraphQLInputObjectField onInputObjectField( - SchemaDirectiveWiringEnvironment environment + public TraversalControl visitGraphQLInputObjectField( + GraphQLInputObjectField field, + TraverserContext context ) { - GraphQLInputObjectField field = environment.getElement(); - var defaultValue = getDefaultValueForSchemaObject(environment); + var defaultValue = getDefaultValueForSchemaObject(context, field.getName()); if (defaultValue != null) { - return field.transform(builder -> builder.defaultValueLiteral(defaultValue).build()); + return changeNode( + context, + field.transform(builder -> builder.defaultValueLiteral(defaultValue).build()) + ); } - return field; + return TraversalControl.CONTINUE; } - private Value getDefaultValueForSchemaObject(SchemaDirectiveWiringEnvironment environment) { + private Value getDefaultValueForSchemaObject( + TraverserContext context, + String name + ) { // Arguments and input fields always have a parent - var parentName = environment.getNodeParentTree().getParentInfo().get().getNode().getName(); - var key = parentName + "_" + environment.getElement().getName(); + var parent = (GraphQLNamedSchemaElement) context.getParentNode(); + var parentName = parent.getName(); + var key = parentName + "_" + name; var preferences = defaultRouteRequest.preferences(); switch (key) { case "planConnection_first": diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java index eca8b1e48c7..4842b5ca0e7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -2,6 +2,7 @@ import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLSchema; +import graphql.schema.SchemaTransformer; import graphql.schema.idl.RuntimeWiring; import graphql.schema.idl.SchemaGenerator; import graphql.schema.idl.SchemaParser; @@ -96,7 +97,7 @@ public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequ URL url = Objects.requireNonNull(SchemaFactory.class.getResource("schema.graphqls")); TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(url.openStream()); IntrospectionTypeWiring typeWiring = new IntrospectionTypeWiring(typeRegistry); - RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring + RuntimeWiring runtimeWiring = RuntimeWiring .newRuntimeWiring() .scalar(GraphQLScalars.DURATION_SCALAR) .scalar(GraphQLScalars.POLYLINE_SCALAR) @@ -182,14 +183,14 @@ public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequ .type(typeWiring.build(TripOccupancyImpl.class)) .type(typeWiring.build(LegTimeImpl.class)) .type(typeWiring.build(RealTimeEstimateImpl.class)) - .type(typeWiring.build(EstimatedTimeImpl.class)); - - if (defaultRouteRequest != null) { - runtimeWiringBuilder.directiveWiring(new DefaultValueDirectiveWiring(defaultRouteRequest)); - } + .type(typeWiring.build(EstimatedTimeImpl.class)) + .build(); SchemaGenerator schemaGenerator = new SchemaGenerator(); - return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiringBuilder.build()); + var schema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); + return defaultRouteRequest != null + ? SchemaTransformer.transformSchema(schema, new DefaultValueInjector(defaultRouteRequest)) + : schema; } catch (Exception e) { LOG.error("Unable to build GTFS GraphQL Schema", e); } From 813bbbfca5d37a633894fff2e7818e5514ed8e22 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 7 Jan 2025 15:05:54 +0200 Subject: [PATCH 15/22] Initialize schema on server start up --- .../org/opentripplanner/apis/gtfs/SchemaFactory.java | 3 ++- .../standalone/configure/ConstructApplication.java | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java index 4842b5ca0e7..165442039c7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -76,6 +76,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; import org.opentripplanner.apis.gtfs.model.StopPosition; +import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.routing.api.request.RouteRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -193,7 +194,7 @@ public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequ : schema; } catch (Exception e) { LOG.error("Unable to build GTFS GraphQL Schema", e); + throw new OtpAppException("Unable to build GTFS GraphQL Schema: " + e.getMessage()); } - return null; } } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 13105e4f898..28ad4dc79a5 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -156,6 +156,7 @@ public DataSource graphOutputDataSource() { private Application createApplication() { LOG.info("Wiring up and configuring server."); setupTransitRoutingServer(); + initializeSchema(); return new OTPWebApplication(routerConfig().server(), this::createServerContext); } @@ -335,4 +336,12 @@ public EmissionsDataModel emissionsDataModel() { public StreetLimitationParameters streetLimitationParameters() { return factory.streetLimitationParameters(); } + + private void initializeSchema() { + if (OTPFeature.GtfsGraphQlApi.isOn()) { + LOG.info("Initializing schema for the GTFS GraphQL API."); + // Initializing the schema service singleton (server shuts down if there is an issue) + factory.schemaService(); + } + } } From 91573593f2e9637c5eaa979fb290ed43eeeda0e8 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Wed, 8 Jan 2025 23:09:24 +0200 Subject: [PATCH 16/22] Remove unnecessary annotation --- .../main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java index 165442039c7..07fc3ddbd09 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -92,7 +92,6 @@ public class SchemaFactory { * @param defaultRouteRequest used to inject defaults into the schema. Doesn't inject any defaults * if the request is not provided. */ - @Nullable public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequest) { try { URL url = Objects.requireNonNull(SchemaFactory.class.getResource("schema.graphqls")); From 7cd815eb8137af0908f74f308691961bfd5a47ff Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Wed, 8 Jan 2025 23:25:41 +0200 Subject: [PATCH 17/22] Split factory method into two versions --- .../apis/gtfs/SchemaFactory.java | 26 +++++++++++++------ .../configure/SchemaServiceModule.java | 2 +- .../apis/gtfs/GraphQLFormattingTest.java | 2 +- .../apis/gtfs/GraphQLIntegrationTest.java | 2 +- .../apis/gtfs/SchemaFactoryTest.java | 8 +++--- .../LegacyRouteRequestMapperTest.java | 2 +- .../routerequest/RouteRequestMapperTest.java | 2 +- 7 files changed, 27 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java index 07fc3ddbd09..ce10ad54565 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/SchemaFactory.java @@ -9,7 +9,6 @@ import graphql.schema.idl.TypeDefinitionRegistry; import java.net.URL; import java.util.Objects; -import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.datafetchers.AgencyImpl; import org.opentripplanner.apis.gtfs.datafetchers.AlertEntityTypeResolver; import org.opentripplanner.apis.gtfs.datafetchers.AlertImpl; @@ -89,10 +88,24 @@ public class SchemaFactory { static final Logger LOG = LoggerFactory.getLogger(SchemaFactory.class); /** - * @param defaultRouteRequest used to inject defaults into the schema. Doesn't inject any defaults - * if the request is not provided. + * Creates schema from schema file and injects default values from code/configuration. + * + * @param defaultRouteRequest used to inject defaults into the schema. */ - public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequest) { + public static GraphQLSchema createSchemaWithDefaultInjection(RouteRequest defaultRouteRequest) { + var originalSchema = createSchema(); + return SchemaTransformer.transformSchema( + originalSchema, + new DefaultValueInjector(defaultRouteRequest) + ); + } + + /** + * Creates schema from schema file without injecting default values from code/configuration. This + * is meant for formatting the schema without editing it or for testing without default + * injection. + */ + public static GraphQLSchema createSchema() { try { URL url = Objects.requireNonNull(SchemaFactory.class.getResource("schema.graphqls")); TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(url.openStream()); @@ -187,10 +200,7 @@ public static GraphQLSchema createSchema(@Nullable RouteRequest defaultRouteRequ .build(); SchemaGenerator schemaGenerator = new SchemaGenerator(); - var schema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); - return defaultRouteRequest != null - ? SchemaTransformer.transformSchema(schema, new DefaultValueInjector(defaultRouteRequest)) - : schema; + return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); } catch (Exception e) { LOG.error("Unable to build GTFS GraphQL Schema", e); throw new OtpAppException("Unable to build GTFS GraphQL Schema: " + e.getMessage()); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java index 1e060b205b5..c1fdd8a7be2 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java @@ -21,7 +21,7 @@ public class SchemaServiceModule { @Singleton public SchemaService provideSchemaService(RouteRequest defaultRouteRequest) { return OTPFeature.GtfsGraphQlApi.isOn() - ? new SchemaService(SchemaFactory.createSchema(defaultRouteRequest)) + ? new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(defaultRouteRequest)) : null; } } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java index edd15d61ba2..24055a7d0f6 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLFormattingTest.java @@ -17,7 +17,7 @@ public class GraphQLFormattingTest { @Test public void format() { String original = readFile(SCHEMA_FILE); - var schema = SchemaFactory.createSchema(null); + var schema = SchemaFactory.createSchema(); writeFile(SCHEMA_FILE, new SchemaPrinter().print(schema)); assertFileEquals(original, SCHEMA_FILE); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index c1feb897b54..12e34dc2bfd 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -356,7 +356,7 @@ public Set findRoutes(StopLocation stop) { defaultVehicleRentalService, new DefaultVehicleParkingService(parkingRepository), realtimeVehicleService, - new SchemaService(SchemaFactory.createSchema(routeRequest)), + new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(routeRequest)), finder, routeRequest ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java index 547ec1ff86b..dac20e3af37 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java @@ -25,7 +25,7 @@ public class SchemaFactoryTest { @Test void createSchema() { - var schema = SchemaFactory.createSchema(new RouteRequest()); + var schema = SchemaFactory.createSchemaWithDefaultInjection(new RouteRequest()); assertNotNull(schema); } @@ -42,7 +42,7 @@ void testDefaultValueInjection() { ); var numItineraries = 63; routeRequest.setNumItineraries(numItineraries); - var schema = SchemaFactory.createSchema(routeRequest); + var schema = SchemaFactory.createSchemaWithDefaultInjection(routeRequest); assertNotNull(schema); var defaultSpeed = (FloatValue) getDefaultValueForField( @@ -71,12 +71,12 @@ void testDefaultValueInjection() { @ParameterizedTest(name = "\"{0}\" must be a an async fetcher") void asyncDataFetchers(String fieldName) { OTPFeature.AsyncGraphQLFetchers.testOn(() -> { - var schema = SchemaFactory.createSchema(new RouteRequest()); + var schema = SchemaFactory.createSchemaWithDefaultInjection(new RouteRequest()); var fetcher = getQueryType(fieldName, schema); assertSame(fetcher.getClass(), AsyncDataFetcher.class); }); OTPFeature.AsyncGraphQLFetchers.testOff(() -> { - var schema = SchemaFactory.createSchema(new RouteRequest()); + var schema = SchemaFactory.createSchemaWithDefaultInjection(new RouteRequest()); var fetcher = getQueryType(fieldName, schema); assertNotSame(fetcher.getClass(), AsyncDataFetcher.class); }); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 84e3e709b67..f359039eb7b 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -68,7 +68,7 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - new SchemaService(SchemaFactory.createSchema(routeRequest)), + new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(routeRequest)), GraphFinder.getInstance(graph, transitService::findRegularStops), routeRequest ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 561ac68b8e7..1360c188608 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -78,7 +78,7 @@ class RouteRequestMapperTest { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - new SchemaService(SchemaFactory.createSchema(routeRequest)), + new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(routeRequest)), GraphFinder.getInstance(graph, transitService::findRegularStops), routeRequest ); From 9f728627f98241360b68f07bf7b59eb3819a308f Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Wed, 8 Jan 2025 23:38:17 +0200 Subject: [PATCH 18/22] Add javadoc --- .../org/opentripplanner/apis/gtfs/DefaultValueInjector.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java index 7c148d6092a..2d8790f8e95 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java @@ -15,6 +15,10 @@ import graphql.util.TraverserContext; import org.opentripplanner.routing.api.request.RouteRequest; +/** + * GraphQL type visitor that injects default values to input fields and query arguments from code + * and configuration. + */ public class DefaultValueInjector extends GraphQLTypeVisitorStub implements GraphQLTypeVisitor { private final RouteRequest defaultRouteRequest; From f7585ccb415c162f1f43f426009a6cfec3733d61 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 10 Jan 2025 12:25:25 +0200 Subject: [PATCH 19/22] Remove service wrapper --- .../apis/gtfs/GraphQLRequestContext.java | 6 ++--- .../apis/gtfs/GtfsGraphQLIndex.java | 2 +- .../SchemaModule.java} | 14 ++++++------ .../apis/gtfs/service/SchemaService.java | 22 ------------------- .../api/OtpServerRequestContext.java | 4 ++-- .../configure/ConstructApplication.java | 4 ++-- .../ConstructApplicationFactory.java | 10 ++++----- .../configure/ConstructApplicationModule.java | 6 ++--- .../server/DefaultServerRequestContext.java | 16 +++++++------- .../apis/gtfs/GraphQLIntegrationTest.java | 3 +-- .../LegacyRouteRequestMapperTest.java | 3 +-- .../routerequest/RouteRequestMapperTest.java | 3 +-- 12 files changed, 34 insertions(+), 59 deletions(-) rename application/src/main/java/org/opentripplanner/apis/gtfs/{service/configure/SchemaServiceModule.java => configure/SchemaModule.java} (52%) delete mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java index 3f3261cbe66..9a3580f474c 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLRequestContext.java @@ -1,6 +1,6 @@ package org.opentripplanner.apis.gtfs; -import org.opentripplanner.apis.gtfs.service.SchemaService; +import graphql.schema.GraphQLSchema; import org.opentripplanner.routing.api.RoutingService; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.fares.FareService; @@ -18,7 +18,7 @@ public record GraphQLRequestContext( VehicleRentalService vehicleRentalService, VehicleParkingService vehicleParkingService, RealtimeVehicleService realTimeVehicleService, - SchemaService schemaService, + GraphQLSchema schema, GraphFinder graphFinder, RouteRequest defaultRouteRequest ) { @@ -30,7 +30,7 @@ public static GraphQLRequestContext ofServerContext(OtpServerRequestContext cont context.vehicleRentalService(), context.vehicleParkingService(), context.realtimeVehicleService(), - context.schemaService(), + context.schema(), context.graphFinder(), context.defaultRouteRequest() ); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 455a4c68042..afb6bfc8802 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -43,7 +43,7 @@ static ExecutionResult getGraphQLExecutionResult( } GraphQL graphQL = GraphQL - .newGraphQL(requestContext.schemaService().schema()) + .newGraphQL(requestContext.schema()) .instrumentation(instrumentation) .defaultDataFetcherExceptionHandler(new LoggingDataFetcherExceptionHandler()) .build(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java b/application/src/main/java/org/opentripplanner/apis/gtfs/configure/SchemaModule.java similarity index 52% rename from application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java rename to application/src/main/java/org/opentripplanner/apis/gtfs/configure/SchemaModule.java index c1fdd8a7be2..bbc7f905b83 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/service/configure/SchemaServiceModule.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/configure/SchemaModule.java @@ -1,27 +1,27 @@ -package org.opentripplanner.apis.gtfs.service.configure; +package org.opentripplanner.apis.gtfs.configure; import dagger.Module; import dagger.Provides; +import graphql.schema.GraphQLSchema; import jakarta.inject.Singleton; import org.opentripplanner.apis.gtfs.SchemaFactory; -import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.api.request.RouteRequest; /** - * The service is used during application serve phase, not loading, and it depends on the default + * The schema is used during application serve phase, not loading, and it depends on the default * route request, which is injected from the - * {@link org.opentripplanner.standalone.config.RouterConfig}. The {@link SchemaService} is only + * {@link org.opentripplanner.standalone.config.RouterConfig}. The {@link GraphQLSchema} is only * constructed if the API feature flag is on. */ @Module -public class SchemaServiceModule { +public class SchemaModule { @Provides @Singleton - public SchemaService provideSchemaService(RouteRequest defaultRouteRequest) { + public GraphQLSchema provideSchema(RouteRequest defaultRouteRequest) { return OTPFeature.GtfsGraphQlApi.isOn() - ? new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(defaultRouteRequest)) + ? SchemaFactory.createSchemaWithDefaultInjection(defaultRouteRequest) : null; } } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java b/application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java deleted file mode 100644 index ba6f2375567..00000000000 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/service/SchemaService.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.opentripplanner.apis.gtfs.service; - -import graphql.schema.GraphQLSchema; -import jakarta.inject.Inject; - -/** - * Service for fetching the {@link GraphQLSchema}. The purpose of this class is to avoid - * reconstructing the schema on each request. - */ -public class SchemaService { - - private final GraphQLSchema schema; - - @Inject - public SchemaService(GraphQLSchema schema) { - this.schema = schema; - } - - public GraphQLSchema schema() { - return schema; - } -} diff --git a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index e52a0834c9d..1884032c952 100644 --- a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -1,10 +1,10 @@ package org.opentripplanner.standalone.api; +import graphql.schema.GraphQLSchema; import io.micrometer.core.instrument.MeterRegistry; import java.util.List; import java.util.Locale; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; import org.opentripplanner.ext.emissions.EmissionsService; @@ -156,5 +156,5 @@ default DataOverlayContext dataOverlayContext(RouteRequest request) { SorlandsbanenNorwayService sorlandsbanenService(); @Nullable - SchemaService schemaService(); + GraphQLSchema schema(); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 28ad4dc79a5..5499e026f2f 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -102,7 +102,7 @@ public class ConstructApplication { .dataImportIssueSummary(issueSummary) .stopConsolidationRepository(stopConsolidationRepository) .streetLimitationParameters(streetLimitationParameters) - .schemaService(config.routerConfig().routingRequestDefaults()) + .schema(config.routerConfig().routingRequestDefaults()) .build(); } @@ -341,7 +341,7 @@ private void initializeSchema() { if (OTPFeature.GtfsGraphQlApi.isOn()) { LOG.info("Initializing schema for the GTFS GraphQL API."); // Initializing the schema service singleton (server shuts down if there is an issue) - factory.schemaService(); + factory.schema(); } } } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index c0ae414e839..b7142a19e30 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -2,10 +2,10 @@ import dagger.BindsInstance; import dagger.Component; +import graphql.schema.GraphQLSchema; import jakarta.inject.Singleton; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.service.SchemaService; -import org.opentripplanner.apis.gtfs.service.configure.SchemaServiceModule; +import org.opentripplanner.apis.gtfs.configure.SchemaModule; import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.emissions.EmissionsServiceModule; import org.opentripplanner.ext.geocoder.LuceneIndex; @@ -65,7 +65,7 @@ RideHailingServicesModule.class, EmissionsServiceModule.class, SorlandsbanenNorwayModule.class, - SchemaServiceModule.class, + SchemaModule.class, StopConsolidationServiceModule.class, InteractiveLauncherModule.class, StreetLimitationParametersServiceModule.class, @@ -107,7 +107,7 @@ public interface ConstructApplicationFactory { SorlandsbanenNorwayService enturSorlandsbanenService(); @Nullable - SchemaService schemaService(); + GraphQLSchema schema(); @Nullable LuceneIndex luceneIndex(); @@ -144,7 +144,7 @@ Builder stopConsolidationRepository( Builder emissionsDataModel(EmissionsDataModel emissionsDataModel); @BindsInstance - Builder schemaService(RouteRequest defaultRouteRequest); + Builder schema(RouteRequest defaultRouteRequest); @BindsInstance Builder streetLimitationParameters(StreetLimitationParameters streetLimitationParameters); diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index e9ddeb51356..34c8a56d9ea 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -2,10 +2,10 @@ import dagger.Module; import dagger.Provides; +import graphql.schema.GraphQLSchema; import io.micrometer.core.instrument.Metrics; import java.util.List; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.geocoder.LuceneIndex; @@ -48,7 +48,7 @@ OtpServerRequestContext providesServerContext( @Nullable TraverseVisitor traverseVisitor, EmissionsService emissionsService, @Nullable SorlandsbanenNorwayService sorlandsbanenService, - @Nullable SchemaService schemaService, + @Nullable GraphQLSchema schema, LauncherRequestDecorator launcherRequestDecorator, @Nullable LuceneIndex luceneIndex ) { @@ -68,7 +68,7 @@ OtpServerRequestContext providesServerContext( vehicleParkingService, emissionsService, sorlandsbanenService, - schemaService, + schema, routerConfig.flexParameters(), rideHailingServices, stopConsolidationService, diff --git a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 1e75998fdf2..f09dd7ef171 100644 --- a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -1,10 +1,10 @@ package org.opentripplanner.standalone.server; +import graphql.schema.GraphQLSchema; import io.micrometer.core.instrument.MeterRegistry; import java.util.List; import java.util.Locale; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.flex.FlexParameters; @@ -54,7 +54,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final EmissionsService emissionsService; @Nullable - private final SchemaService schemaService; + private final GraphQLSchema schema; @Nullable private final SorlandsbanenNorwayService sorlandsbanenService; @@ -84,7 +84,7 @@ private DefaultServerRequestContext( VehicleParkingService vehicleParkingService, @Nullable EmissionsService emissionsService, @Nullable SorlandsbanenNorwayService sorlandsbanenService, - @Nullable SchemaService schemaService, + @Nullable GraphQLSchema schema, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, StreetLimitationParametersService streetLimitationParametersService, @@ -110,7 +110,7 @@ private DefaultServerRequestContext( this.rideHailingServices = rideHailingServices; this.emissionsService = emissionsService; this.sorlandsbanenService = sorlandsbanenService; - this.schemaService = schemaService; + this.schema = schema; this.stopConsolidationService = stopConsolidationService; this.streetLimitationParametersService = streetLimitationParametersService; this.luceneIndex = luceneIndex; @@ -134,7 +134,7 @@ public static DefaultServerRequestContext create( VehicleParkingService vehicleParkingService, @Nullable EmissionsService emissionsService, @Nullable SorlandsbanenNorwayService sorlandsbanenService, - @Nullable SchemaService schemaService, + @Nullable GraphQLSchema schema, FlexParameters flexParameters, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, @@ -158,7 +158,7 @@ public static DefaultServerRequestContext create( vehicleParkingService, emissionsService, sorlandsbanenService, - schemaService, + schema, rideHailingServices, stopConsolidationService, streetLimitationParametersService, @@ -243,8 +243,8 @@ public List rideHailingServices() { @Nullable @Override - public SchemaService schemaService() { - return schemaService; + public GraphQLSchema schema() { + return schema; } @Override diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 12e34dc2bfd..3d23f6ae580 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -37,7 +37,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner._support.text.I18NStrings; -import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.ext.fares.FaresToItineraryMapper; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.framework.geometry.WgsCoordinate; @@ -356,7 +355,7 @@ public Set findRoutes(StopLocation stop) { defaultVehicleRentalService, new DefaultVehicleParkingService(parkingRepository), realtimeVehicleService, - new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(routeRequest)), + SchemaFactory.createSchemaWithDefaultInjection(routeRequest), finder, routeRequest ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 062313d876c..bf019023eaa 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -27,7 +27,6 @@ import org.opentripplanner.apis.gtfs.SchemaFactory; import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; -import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.api.request.RouteRequest; @@ -68,7 +67,7 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(routeRequest)), + SchemaFactory.createSchemaWithDefaultInjection(routeRequest), GraphFinder.getInstance(graph, transitService::findRegularStopsByBoundingBox), routeRequest ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 1b949a4173f..0aab9473371 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -25,7 +25,6 @@ import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.SchemaFactory; import org.opentripplanner.apis.gtfs.TestRoutingService; -import org.opentripplanner.apis.gtfs.service.SchemaService; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.request.RouteRequest; @@ -78,7 +77,7 @@ class RouteRequestMapperTest { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - new SchemaService(SchemaFactory.createSchemaWithDefaultInjection(routeRequest)), + SchemaFactory.createSchemaWithDefaultInjection(routeRequest), GraphFinder.getInstance(graph, transitService::findRegularStopsByBoundingBox), routeRequest ); From 69de2708cd4cecbcbe6db8b03e75613c58bd91a7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 10 Jan 2025 12:27:59 +0200 Subject: [PATCH 20/22] Fix comment --- .../standalone/configure/ConstructApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 5499e026f2f..183bb0b5b8e 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -340,7 +340,7 @@ public StreetLimitationParameters streetLimitationParameters() { private void initializeSchema() { if (OTPFeature.GtfsGraphQlApi.isOn()) { LOG.info("Initializing schema for the GTFS GraphQL API."); - // Initializing the schema service singleton (server shuts down if there is an issue) + // Initializing the schema singleton (server shuts down if there is an issue) factory.schema(); } } From fc7c77f61689c5717cd02a04acd47795864fb010 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 14 Jan 2025 14:04:36 +0200 Subject: [PATCH 21/22] Add missing annotation --- .../org/opentripplanner/apis/gtfs/configure/SchemaModule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/configure/SchemaModule.java b/application/src/main/java/org/opentripplanner/apis/gtfs/configure/SchemaModule.java index bbc7f905b83..45db5a366dd 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/configure/SchemaModule.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/configure/SchemaModule.java @@ -4,6 +4,7 @@ import dagger.Provides; import graphql.schema.GraphQLSchema; import jakarta.inject.Singleton; +import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.SchemaFactory; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.routing.api.request.RouteRequest; @@ -19,6 +20,7 @@ public class SchemaModule { @Provides @Singleton + @Nullable public GraphQLSchema provideSchema(RouteRequest defaultRouteRequest) { return OTPFeature.GtfsGraphQlApi.isOn() ? SchemaFactory.createSchemaWithDefaultInjection(defaultRouteRequest) From 76db9b5d563ffbc6a007abca612a153173f8dfbd Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 14 Jan 2025 15:24:09 +0200 Subject: [PATCH 22/22] Refactor defaults --- .../apis/gtfs/DefaultValueInjector.java | 211 ++++++++++-------- 1 file changed, 124 insertions(+), 87 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java index 2d8790f8e95..717c6f69431 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java @@ -13,6 +13,9 @@ import graphql.schema.GraphQLTypeVisitorStub; import graphql.util.TraversalControl; import graphql.util.TraverserContext; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; import org.opentripplanner.routing.api.request.RouteRequest; /** @@ -21,10 +24,10 @@ */ public class DefaultValueInjector extends GraphQLTypeVisitorStub implements GraphQLTypeVisitor { - private final RouteRequest defaultRouteRequest; + private final Map defaultForKey; public DefaultValueInjector(RouteRequest defaultRouteRequest) { - this.defaultRouteRequest = defaultRouteRequest; + this.defaultForKey = createDefaultMapping(defaultRouteRequest); } @Override @@ -64,92 +67,126 @@ private Value getDefaultValueForSchemaObject( // Arguments and input fields always have a parent var parent = (GraphQLNamedSchemaElement) context.getParentNode(); var parentName = parent.getName(); - var key = parentName + "_" + name; + var key = parentName + "." + name; + return defaultForKey.get(key); + } + + private static Map createDefaultMapping(RouteRequest defaultRouteRequest) { + var builder = new DefaultMappingBuilder(); var preferences = defaultRouteRequest.preferences(); - switch (key) { - case "planConnection_first": - return IntValue.of(defaultRouteRequest.numItineraries()); - case "planConnection_searchWindow": - return defaultRouteRequest.searchWindow() != null - ? StringValue.of(defaultRouteRequest.searchWindow().toString()) - : null; - case "AlightPreferencesInput_slack": - return StringValue.of(preferences.transit().alightSlack().defaultValue().toString()); - case "BicycleParkingPreferencesInput_unpreferredCost": - return IntValue.of( - preferences.bike().parking().unpreferredVehicleParkingTagCost().toSeconds() - ); - case "BicyclePreferencesInput_boardCost": - return IntValue.of(preferences.bike().boardCost()); - case "BicyclePreferencesInput_reluctance": - return FloatValue.of(preferences.bike().reluctance()); - case "BicyclePreferencesInput_speed": - return FloatValue.of(preferences.bike().speed()); - case "BicycleWalkPreferencesCostInput_mountDismountCost": - return IntValue.of(preferences.bike().walking().mountDismountCost().toSeconds()); - case "BicycleWalkPreferencesCostInput_reluctance": - return FloatValue.of(preferences.bike().walking().reluctance()); - case "BicycleWalkPreferencesInput_mountDismountTime": - return StringValue.of(preferences.bike().walking().mountDismountTime().toString()); - case "BicycleWalkPreferencesInput_speed": - return FloatValue.of(preferences.bike().walking().speed()); - case "BoardPreferencesInput_slack": - return StringValue.of(preferences.transit().boardSlack().defaultValue().toString()); - case "BoardPreferencesInput_waitReluctance": - return FloatValue.of(preferences.transfer().waitReluctance()); - case "CarParkingPreferencesInput_unpreferredCost": - return IntValue.of( - preferences.car().parking().unpreferredVehicleParkingTagCost().toSeconds() - ); - case "CarPreferencesInput_reluctance": - return FloatValue.of(preferences.car().reluctance()); - case "DestinationBicyclePolicyInput_allowKeeping": - return BooleanValue.of( - preferences.bike().rental().allowArrivingInRentedVehicleAtDestination() - ); - case "DestinationBicyclePolicyInput_keepingCost": - return IntValue.of( - preferences.bike().rental().arrivingInRentalVehicleAtDestinationCost().toSeconds() - ); - case "DestinationScooterPolicyInput_allowKeeping": - return BooleanValue.of( - preferences.scooter().rental().allowArrivingInRentedVehicleAtDestination() - ); - case "DestinationScooterPolicyInput_keepingCost": - return IntValue.of( - preferences.scooter().rental().arrivingInRentalVehicleAtDestinationCost().toSeconds() - ); - case "ScooterPreferencesInput_reluctance": - return FloatValue.of(preferences.scooter().reluctance()); - case "ScooterPreferencesInput_speed": - return FloatValue.of(preferences.scooter().speed()); - case "TimetablePreferencesInput_excludeRealTimeUpdates": - return BooleanValue.of(preferences.transit().ignoreRealtimeUpdates()); - case "TimetablePreferencesInput_includePlannedCancellations": - return BooleanValue.of(preferences.transit().includePlannedCancellations()); - case "TimetablePreferencesInput_includeRealTimeCancellations": - return BooleanValue.of(preferences.transit().includeRealtimeCancellations()); - case "TransferPreferencesInput_cost": - return IntValue.of(preferences.transfer().cost()); - case "TransferPreferencesInput_maximumAdditionalTransfers": - return IntValue.of(preferences.transfer().maxAdditionalTransfers()); - case "TransferPreferencesInput_maximumTransfers": - // Max transfers are wrong in the internal model but fixed in the API mapping - return IntValue.of(preferences.transfer().maxTransfers() - 1); - case "TransferPreferencesInput_slack": - return StringValue.of(preferences.transfer().slack().toString()); - case "WalkPreferencesInput_boardCost": - return IntValue.of(preferences.walk().boardCost()); - case "WalkPreferencesInput_reluctance": - return FloatValue.of(preferences.walk().reluctance()); - case "WalkPreferencesInput_safetyFactor": - return FloatValue.of(preferences.walk().safetyFactor()); - case "WalkPreferencesInput_speed": - return FloatValue.of(preferences.walk().speed()); - case "WheelchairPreferencesInput_enabled": - return BooleanValue.of(defaultRouteRequest.wheelchair()); - default: - return null; + return builder + .intReq("planConnection.first", defaultRouteRequest.numItineraries()) + .stringOpt("planConnection.searchWindow", defaultRouteRequest.searchWindow()) + .stringReq("AlightPreferencesInput.slack", preferences.transit().alightSlack().defaultValue()) + .intReq( + "BicycleParkingPreferencesInput.unpreferredCost", + preferences.bike().parking().unpreferredVehicleParkingTagCost().toSeconds() + ) + .intReq("BicyclePreferencesInput.boardCost", preferences.bike().boardCost()) + .floatReq("BicyclePreferencesInput.reluctance", preferences.bike().reluctance()) + .floatReq("BicyclePreferencesInput.speed", preferences.bike().speed()) + .intReq( + "BicycleWalkPreferencesCostInput.mountDismountCost", + preferences.bike().walking().mountDismountCost().toSeconds() + ) + .floatReq( + "BicycleWalkPreferencesCostInput.reluctance", + preferences.bike().walking().reluctance() + ) + .stringReq( + "BicycleWalkPreferencesInput.mountDismountTime", + preferences.bike().walking().mountDismountTime() + ) + .floatReq("BicycleWalkPreferencesInput.speed", preferences.bike().walking().speed()) + .stringReq("BoardPreferencesInput.slack", preferences.transit().boardSlack().defaultValue()) + .floatReq("BoardPreferencesInput.waitReluctance", preferences.transfer().waitReluctance()) + .intReq( + "CarParkingPreferencesInput.unpreferredCost", + preferences.car().parking().unpreferredVehicleParkingTagCost().toSeconds() + ) + .floatReq("CarPreferencesInput.reluctance", preferences.car().reluctance()) + .boolReq( + "DestinationBicyclePolicyInput.allowKeeping", + preferences.bike().rental().allowArrivingInRentedVehicleAtDestination() + ) + .intReq( + "DestinationBicyclePolicyInput.keepingCost", + preferences.bike().rental().arrivingInRentalVehicleAtDestinationCost().toSeconds() + ) + .boolReq( + "DestinationScooterPolicyInput.allowKeeping", + preferences.scooter().rental().allowArrivingInRentedVehicleAtDestination() + ) + .intReq( + "DestinationScooterPolicyInput.keepingCost", + preferences.scooter().rental().arrivingInRentalVehicleAtDestinationCost().toSeconds() + ) + .floatReq("ScooterPreferencesInput.reluctance", preferences.scooter().reluctance()) + .floatReq("ScooterPreferencesInput.speed", preferences.scooter().speed()) + .boolReq( + "TimetablePreferencesInput.excludeRealTimeUpdates", + preferences.transit().ignoreRealtimeUpdates() + ) + .boolReq( + "TimetablePreferencesInput.includePlannedCancellations", + preferences.transit().includePlannedCancellations() + ) + .boolReq( + "TimetablePreferencesInput.includeRealTimeCancellations", + preferences.transit().includeRealtimeCancellations() + ) + .intReq("TransferPreferencesInput.cost", preferences.transfer().cost()) + .intReq( + "TransferPreferencesInput.maximumAdditionalTransfers", + preferences.transfer().maxAdditionalTransfers() + ) + // Max transfers are wrong in the internal model but fixed in the API mapping + .intReq( + "TransferPreferencesInput.maximumTransfers", + preferences.transfer().maxTransfers() - 1 + ) + .stringReq("TransferPreferencesInput.slack", preferences.transfer().slack()) + .intReq("WalkPreferencesInput.boardCost", preferences.walk().boardCost()) + .floatReq("WalkPreferencesInput.reluctance", preferences.walk().reluctance()) + .floatReq("WalkPreferencesInput.safetyFactor", preferences.walk().safetyFactor()) + .floatReq("WalkPreferencesInput.speed", preferences.walk().speed()) + .boolReq("WheelchairPreferencesInput.enabled", defaultRouteRequest.wheelchair()) + .build(); + } + + private static class DefaultMappingBuilder { + + private final Map defaultValueForKey = new HashMap(); + + public DefaultMappingBuilder intReq(String key, int value) { + defaultValueForKey.put(key, IntValue.of(value)); + return this; + } + + public DefaultMappingBuilder floatReq(String key, double value) { + defaultValueForKey.put(key, FloatValue.of(value)); + return this; + } + + public DefaultMappingBuilder stringReq(String key, Object value) { + defaultValueForKey.put(key, StringValue.of(value.toString())); + return this; + } + + public DefaultMappingBuilder stringOpt(String key, @Nullable Object value) { + if (value != null) { + defaultValueForKey.put(key, StringValue.of(value.toString())); + } + return this; + } + + public DefaultMappingBuilder boolReq(String key, boolean value) { + defaultValueForKey.put(key, BooleanValue.of(value)); + return this; + } + + public Map build() { + return defaultValueForKey; } } }