Skip to content

Commit e3f396e

Browse files
committed
Merge branch 'plan-connection' into dev-2.x
2 parents 92458a6 + 18bbe02 commit e3f396e

File tree

51 files changed

+7284
-643
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+7284
-643
lines changed

pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -852,12 +852,12 @@
852852
<dependency>
853853
<groupId>com.graphql-java</groupId>
854854
<artifactId>graphql-java</artifactId>
855-
<version>21.5</version>
855+
<version>22.0</version>
856856
</dependency>
857857
<dependency>
858858
<groupId>com.graphql-java</groupId>
859859
<artifactId>graphql-java-extended-scalars</artifactId>
860-
<version>21.0</version>
860+
<version>22.0</version>
861861
</dependency>
862862
<dependency>
863863
<groupId>org.apache.httpcomponents.client5</groupId>

src/ext/java/org/opentripplanner/ext/actuator/MicrometerGraphQLInstrumentation.java

+1-11
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
1414
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
1515
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
16-
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
1716
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
1817
import graphql.language.Document;
1918
import graphql.schema.GraphQLTypeUtil;
@@ -22,7 +21,6 @@
2221
import io.micrometer.core.instrument.Tag;
2322
import io.micrometer.core.instrument.Timer;
2423
import java.util.List;
25-
import java.util.concurrent.CompletableFuture;
2624

2725
/**
2826
* Using this instrumentation we can precisely measure how queries and data fetchers are executed
@@ -107,21 +105,13 @@ public ExecutionStrategyInstrumentationContext beginExecutionStrategy(
107105
) {
108106
return new ExecutionStrategyInstrumentationContext() {
109107
@Override
110-
public void onDispatched(CompletableFuture<ExecutionResult> result) {}
108+
public void onDispatched() {}
111109

112110
@Override
113111
public void onCompleted(ExecutionResult result, Throwable t) {}
114112
};
115113
}
116114

117-
@Override
118-
public InstrumentationContext<ExecutionResult> beginField(
119-
InstrumentationFieldParameters parameters,
120-
InstrumentationState state
121-
) {
122-
return noOp();
123-
}
124-
125115
@Override
126116
public InstrumentationContext<Object> beginFieldFetch(
127117
InstrumentationFieldFetchParameters parameters,

src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java

+275-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.bedatadriven.jackson.datatype.jts.JtsModule;
44
import com.fasterxml.jackson.databind.JsonNode;
55
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import graphql.language.FloatValue;
7+
import graphql.language.IntValue;
68
import graphql.language.StringValue;
79
import graphql.relay.Relay;
810
import graphql.schema.Coercing;
@@ -14,18 +16,20 @@
1416
import java.time.OffsetDateTime;
1517
import java.time.ZonedDateTime;
1618
import java.time.format.DateTimeFormatter;
19+
import java.util.Optional;
1720
import javax.annotation.Nonnull;
1821
import org.locationtech.jts.geom.Geometry;
1922
import org.opentripplanner.framework.geometry.GeometryUtils;
2023
import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory;
24+
import org.opentripplanner.framework.model.Cost;
2125
import org.opentripplanner.framework.model.Grams;
2226
import org.opentripplanner.framework.time.OffsetDateTimeParser;
2327

2428
public class GraphQLScalars {
2529

26-
private static final ObjectMapper geoJsonMapper = new ObjectMapper()
30+
private static final ObjectMapper GEOJSON_MAPPER = new ObjectMapper()
2731
.registerModule(new JtsModule(GeometryUtils.getGeometryFactory()));
28-
public static GraphQLScalarType DURATION_SCALAR = DurationScalarFactory.createDurationScalar();
32+
public static final GraphQLScalarType DURATION_SCALAR = DurationScalarFactory.createDurationScalar();
2933

3034
public static final GraphQLScalarType POLYLINE_SCALAR = GraphQLScalarType
3135
.newScalar()
@@ -112,6 +116,127 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce
112116
)
113117
.build();
114118

119+
public static final GraphQLScalarType COORDINATE_VALUE_SCALAR = GraphQLScalarType
120+
.newScalar()
121+
.name("CoordinateValue")
122+
.coercing(
123+
new Coercing<Double, Double>() {
124+
private static final String VALIDATION_ERROR_MESSAGE = "Not a valid WGS84 coordinate value";
125+
126+
@Override
127+
public Double serialize(@Nonnull Object dataFetcherResult)
128+
throws CoercingSerializeException {
129+
if (dataFetcherResult instanceof Double doubleValue) {
130+
return doubleValue;
131+
} else if (dataFetcherResult instanceof Float floatValue) {
132+
return floatValue.doubleValue();
133+
} else {
134+
throw new CoercingSerializeException(
135+
"Cannot serialize object of class %s as a coordinate number".formatted(
136+
dataFetcherResult.getClass().getSimpleName()
137+
)
138+
);
139+
}
140+
}
141+
142+
@Override
143+
public Double parseValue(Object input) throws CoercingParseValueException {
144+
if (input instanceof Double doubleValue) {
145+
return validateCoordinate(doubleValue)
146+
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
147+
}
148+
if (input instanceof Integer intValue) {
149+
return validateCoordinate(intValue)
150+
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
151+
}
152+
throw new CoercingParseValueException(
153+
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
154+
);
155+
}
156+
157+
@Override
158+
public Double parseLiteral(Object input) throws CoercingParseLiteralException {
159+
if (input instanceof FloatValue coordinate) {
160+
return validateCoordinate(coordinate.getValue().doubleValue())
161+
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
162+
}
163+
if (input instanceof IntValue coordinate) {
164+
return validateCoordinate(coordinate.getValue().doubleValue())
165+
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
166+
}
167+
throw new CoercingParseLiteralException(
168+
"Expected a number, got: " + input.getClass().getSimpleName()
169+
);
170+
}
171+
172+
private static Optional<Double> validateCoordinate(double coordinate) {
173+
if (coordinate >= -180.001 && coordinate <= 180.001) {
174+
return Optional.of(coordinate);
175+
}
176+
return Optional.empty();
177+
}
178+
}
179+
)
180+
.build();
181+
182+
public static final GraphQLScalarType COST_SCALAR = GraphQLScalarType
183+
.newScalar()
184+
.name("Cost")
185+
.coercing(
186+
new Coercing<Cost, Integer>() {
187+
private static final int MAX_COST = 1000000;
188+
private static final String VALIDATION_ERROR_MESSAGE =
189+
"Cost cannot be negative or greater than %d".formatted(MAX_COST);
190+
191+
@Override
192+
public Integer serialize(@Nonnull Object dataFetcherResult)
193+
throws CoercingSerializeException {
194+
if (dataFetcherResult instanceof Integer intValue) {
195+
return intValue;
196+
} else if (dataFetcherResult instanceof Cost costValue) {
197+
return costValue.toSeconds();
198+
} else {
199+
throw new CoercingSerializeException(
200+
"Cannot serialize object of class %s as a cost".formatted(
201+
dataFetcherResult.getClass().getSimpleName()
202+
)
203+
);
204+
}
205+
}
206+
207+
@Override
208+
public Cost parseValue(Object input) throws CoercingParseValueException {
209+
if (input instanceof Integer intValue) {
210+
return validateCost(intValue)
211+
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
212+
}
213+
throw new CoercingParseValueException(
214+
"Expected an integer, got %s %s".formatted(input.getClass().getSimpleName(), input)
215+
);
216+
}
217+
218+
@Override
219+
public Cost parseLiteral(Object input) throws CoercingParseLiteralException {
220+
if (input instanceof IntValue intValue) {
221+
var value = intValue.getValue().intValue();
222+
return validateCost(value)
223+
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
224+
}
225+
throw new CoercingParseLiteralException(
226+
"Expected an integer, got: " + input.getClass().getSimpleName()
227+
);
228+
}
229+
230+
private static Optional<Cost> validateCost(int cost) {
231+
if (cost >= 0 && cost <= MAX_COST) {
232+
return Optional.of(Cost.costOfSeconds(cost));
233+
}
234+
return Optional.empty();
235+
}
236+
}
237+
)
238+
.build();
239+
115240
public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType
116241
.newScalar()
117242
.name("GeoJson")
@@ -122,7 +247,7 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce
122247
public JsonNode serialize(Object dataFetcherResult) throws CoercingSerializeException {
123248
if (dataFetcherResult instanceof Geometry) {
124249
var geom = (Geometry) dataFetcherResult;
125-
return geoJsonMapper.valueToTree(geom);
250+
return GEOJSON_MAPPER.valueToTree(geom);
126251
}
127252
return null;
128253
}
@@ -196,20 +321,159 @@ public Double serialize(Object dataFetcherResult) throws CoercingSerializeExcept
196321

197322
@Override
198323
public Grams parseValue(Object input) throws CoercingParseValueException {
199-
if (input instanceof Double) {
200-
var grams = (Double) input;
201-
return new Grams(grams);
324+
if (input instanceof Double doubleValue) {
325+
return new Grams(doubleValue);
202326
}
203-
return null;
327+
if (input instanceof Integer intValue) {
328+
return new Grams(intValue);
329+
}
330+
throw new CoercingParseValueException(
331+
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
332+
);
204333
}
205334

206335
@Override
207336
public Grams parseLiteral(Object input) throws CoercingParseLiteralException {
208-
if (input instanceof Double) {
209-
var grams = (Double) input;
210-
return new Grams(grams);
337+
if (input instanceof FloatValue coordinate) {
338+
return new Grams(coordinate.getValue().doubleValue());
211339
}
212-
return null;
340+
if (input instanceof IntValue coordinate) {
341+
return new Grams(coordinate.getValue().doubleValue());
342+
}
343+
throw new CoercingParseLiteralException(
344+
"Expected a number, got: " + input.getClass().getSimpleName()
345+
);
346+
}
347+
}
348+
)
349+
.build();
350+
351+
public static final GraphQLScalarType RATIO_SCALAR = GraphQLScalarType
352+
.newScalar()
353+
.name("Ratio")
354+
.coercing(
355+
new Coercing<Double, Double>() {
356+
private static final String VALIDATION_ERROR_MESSAGE =
357+
"Value is under 0 or greater than 1.";
358+
359+
@Override
360+
public Double serialize(@Nonnull Object dataFetcherResult)
361+
throws CoercingSerializeException {
362+
var validationException = new CoercingSerializeException(VALIDATION_ERROR_MESSAGE);
363+
if (dataFetcherResult instanceof Double doubleValue) {
364+
return validateRatio(doubleValue).orElseThrow(() -> validationException);
365+
} else if (dataFetcherResult instanceof Float floatValue) {
366+
return validateRatio(floatValue.doubleValue()).orElseThrow(() -> validationException);
367+
} else {
368+
throw new CoercingSerializeException(
369+
"Cannot serialize object of class %s as a ratio".formatted(
370+
dataFetcherResult.getClass().getSimpleName()
371+
)
372+
);
373+
}
374+
}
375+
376+
@Override
377+
public Double parseValue(Object input) throws CoercingParseValueException {
378+
if (input instanceof Double doubleValue) {
379+
return validateRatio(doubleValue)
380+
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
381+
}
382+
if (input instanceof Integer intValue) {
383+
return validateRatio(intValue)
384+
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
385+
}
386+
throw new CoercingParseValueException(
387+
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
388+
);
389+
}
390+
391+
@Override
392+
public Double parseLiteral(Object input) throws CoercingParseLiteralException {
393+
if (input instanceof FloatValue ratio) {
394+
return validateRatio(ratio.getValue().doubleValue())
395+
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
396+
}
397+
if (input instanceof IntValue ratio) {
398+
return validateRatio(ratio.getValue().doubleValue())
399+
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
400+
}
401+
throw new CoercingParseLiteralException(
402+
"Expected a number, got: " + input.getClass().getSimpleName()
403+
);
404+
}
405+
406+
private static Optional<Double> validateRatio(double ratio) {
407+
if (ratio >= -0.001 && ratio <= 1.001) {
408+
return Optional.of(ratio);
409+
}
410+
return Optional.empty();
411+
}
412+
}
413+
)
414+
.build();
415+
416+
public static final GraphQLScalarType RELUCTANCE_SCALAR = GraphQLScalarType
417+
.newScalar()
418+
.name("Reluctance")
419+
.coercing(
420+
new Coercing<Double, Double>() {
421+
private static final double MIN_Reluctance = 0.1;
422+
private static final double MAX_Reluctance = 100000;
423+
private static final String VALIDATION_ERROR_MESSAGE =
424+
"Reluctance needs to be between %s and %s".formatted(MIN_Reluctance, MAX_Reluctance);
425+
426+
@Override
427+
public Double serialize(@Nonnull Object dataFetcherResult)
428+
throws CoercingSerializeException {
429+
if (dataFetcherResult instanceof Double doubleValue) {
430+
return doubleValue;
431+
} else if (dataFetcherResult instanceof Float floatValue) {
432+
return floatValue.doubleValue();
433+
} else {
434+
throw new CoercingSerializeException(
435+
"Cannot serialize object of class %s as a reluctance".formatted(
436+
dataFetcherResult.getClass().getSimpleName()
437+
)
438+
);
439+
}
440+
}
441+
442+
@Override
443+
public Double parseValue(Object input) throws CoercingParseValueException {
444+
if (input instanceof Double doubleValue) {
445+
return validateReluctance(doubleValue)
446+
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
447+
}
448+
if (input instanceof Integer intValue) {
449+
return validateReluctance(intValue)
450+
.orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE));
451+
}
452+
throw new CoercingParseValueException(
453+
"Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input)
454+
);
455+
}
456+
457+
@Override
458+
public Double parseLiteral(Object input) throws CoercingParseLiteralException {
459+
if (input instanceof FloatValue reluctance) {
460+
return validateReluctance(reluctance.getValue().doubleValue())
461+
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
462+
}
463+
if (input instanceof IntValue reluctance) {
464+
return validateReluctance(reluctance.getValue().doubleValue())
465+
.orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE));
466+
}
467+
throw new CoercingParseLiteralException(
468+
"Expected a number, got: " + input.getClass().getSimpleName()
469+
);
470+
}
471+
472+
private static Optional<Double> validateReluctance(double reluctance) {
473+
if (reluctance >= MIN_Reluctance - 0.001 && reluctance <= MAX_Reluctance + 0.001) {
474+
return Optional.of(reluctance);
475+
}
476+
return Optional.empty();
213477
}
214478
}
215479
)

0 commit comments

Comments
 (0)