diff --git a/.github/trigger_files/beam_PreCommit_Python_PVR_Flink.json b/.github/trigger_files/beam_PreCommit_Python_PVR_Flink.json new file mode 100644 index 000000000000..616d37428c01 --- /dev/null +++ b/.github/trigger_files/beam_PreCommit_Python_PVR_Flink.json @@ -0,0 +1,4 @@ +{ + "comment": "Modify this file in a trivial way to cause this test suite to run", + "revision": 1 +} diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java index ac76a57b6b07..b9061e1734cf 100644 --- a/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java +++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java @@ -502,13 +502,6 @@ Class getProxyClass() { new ObjectMapper() .registerModules(ObjectMapper.findModules(ReflectHelpers.findClassLoader())); - private static final DefaultDeserializationContext DESERIALIZATION_CONTEXT = - new DefaultDeserializationContext.Impl(MAPPER.getDeserializationContext().getFactory()) - .createInstance( - MAPPER.getDeserializationConfig(), - new TokenBuffer(MAPPER, false).asParser(), - new InjectableValues.Std()); - static final DefaultSerializerProvider SERIALIZER_PROVIDER = new DefaultSerializerProvider.Impl() .createInstance(MAPPER.getSerializationConfig(), MAPPER.getSerializerFactory()); @@ -1733,7 +1726,12 @@ private static JsonDeserializer computeDeserializerForMethod(Method meth BeanProperty prop = createBeanProperty(method); AnnotatedMember annotatedMethod = prop.getMember(); - DefaultDeserializationContext context = DESERIALIZATION_CONTEXT.copy(); + // Initialize a new context that is properly associated with a dummy parser. + // Using copy() here would leave the context's transient parser field as null, + // causing NullPointerExceptions in Jackson 2.14+ when deserializers try to + // query the parser for format constraints or coercion validations. + JsonParser dummyParser = new TokenBuffer(MAPPER, false).asParser(); + DefaultDeserializationContext context = createDeserializationContext(dummyParser); Object maybeDeserializerClass = context.getAnnotationIntrospector().findDeserializer(annotatedMethod); @@ -1756,6 +1754,11 @@ private static JsonDeserializer computeDeserializerForMethod(Method meth } } + private static DefaultDeserializationContext createDeserializationContext(JsonParser parser) { + return ((DefaultDeserializationContext) MAPPER.getDeserializationContext()) + .createInstance(MAPPER.getDeserializationConfig(), parser, new InjectableValues.Std()); + } + private static Optional> computeCustomSerializerForMethod(Method method) { try { BeanProperty prop = createBeanProperty(method); @@ -1805,7 +1808,12 @@ static Object deserializeNode(JsonNode node, Method method) throws IOException { parser.nextToken(); JsonDeserializer jsonDeserializer = getDeserializerForMethod(method); - return jsonDeserializer.deserialize(parser, DESERIALIZATION_CONTEXT.copy()); + // Create a fresh context that is correctly associated with the active parser. + // Using copy() here would leave the context's transient parser field as null, + // causing NullPointerExceptions in Jackson 2.14+ deserializers during coercion + // validations and length checks. + DefaultDeserializationContext context = createDeserializationContext(parser); + return jsonDeserializer.deserialize(parser, context); } /**