You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by ji...@apache.org on 2021/01/26 21:36:11 UTC

[druid] 02/02: Cleaner handling for Jackson annotations

This is an automated email from the ASF dual-hosted git repository.

jihoonson pushed a commit to branch 0.20.1
in repository https://gitbox.apache.org/repos/asf/druid.git

commit ae4b1920c53d34008ab55cfa2e368a8affad77a0
Author: Jihoon Son <ji...@apache.org>
AuthorDate: Tue Jan 26 12:57:09 2021 -0800

    Cleaner handling for Jackson annotations
---
 .../apache/druid/guice/DruidSecondaryModule.java   |  26 +-
 .../druid/guice/GuiceAnnotationIntrospector.java   |  54 ++++
 .../data/input/impl/DimensionsSpecSerdeTest.java   |   5 +
 .../data/input/impl/StringDimensionSchemaTest.java |  63 ++++
 .../druid/guice/DruidSecondaryModuleTest.java      | 353 +++++++++++++++++++++
 .../apache/druid/indexing/common/TestUtils.java    |   3 +
 .../java/org/apache/druid/segment/TestHelper.java  |  22 ++
 7 files changed, 521 insertions(+), 5 deletions(-)

diff --git a/core/src/main/java/org/apache/druid/guice/DruidSecondaryModule.java b/core/src/main/java/org/apache/druid/guice/DruidSecondaryModule.java
index 319c0d1..5e4db78 100644
--- a/core/src/main/java/org/apache/druid/guice/DruidSecondaryModule.java
+++ b/core/src/main/java/org/apache/druid/guice/DruidSecondaryModule.java
@@ -19,8 +19,10 @@
 
 package org.apache.druid.guice;
 
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.inject.Binder;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
@@ -83,14 +85,28 @@ public class DruidSecondaryModule implements Module
     return smileMapper;
   }
 
-  private void setupJackson(Injector injector, final ObjectMapper mapper)
+  @VisibleForTesting
+  public static void setupJackson(Injector injector, final ObjectMapper mapper)
   {
-    final GuiceAnnotationIntrospector guiceIntrospector = new GuiceAnnotationIntrospector();
-
     mapper.setInjectableValues(new GuiceInjectableValues(injector));
+    setupAnnotationIntrospector(mapper, new GuiceAnnotationIntrospector());
+  }
+
+  @VisibleForTesting
+  public static void setupAnnotationIntrospector(
+      final ObjectMapper mapper,
+      final AnnotationIntrospector annotationIntrospector
+  )
+  {
     mapper.setAnnotationIntrospectors(
-        new AnnotationIntrospectorPair(guiceIntrospector, mapper.getSerializationConfig().getAnnotationIntrospector()),
-        new AnnotationIntrospectorPair(guiceIntrospector, mapper.getDeserializationConfig().getAnnotationIntrospector())
+        new AnnotationIntrospectorPair(
+            annotationIntrospector,
+            mapper.getSerializationConfig().getAnnotationIntrospector()
+        ),
+        new AnnotationIntrospectorPair(
+            annotationIntrospector,
+            mapper.getDeserializationConfig().getAnnotationIntrospector()
+        )
     );
   }
 }
diff --git a/core/src/main/java/org/apache/druid/guice/GuiceAnnotationIntrospector.java b/core/src/main/java/org/apache/druid/guice/GuiceAnnotationIntrospector.java
index 7862c97..29fac1a 100644
--- a/core/src/main/java/org/apache/druid/guice/GuiceAnnotationIntrospector.java
+++ b/core/src/main/java/org/apache/druid/guice/GuiceAnnotationIntrospector.java
@@ -20,8 +20,12 @@
 package org.apache.druid.guice;
 
 import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.introspect.Annotated;
 import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
 import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
 import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
 import com.google.inject.BindingAnnotation;
 import com.google.inject.Key;
@@ -56,4 +60,54 @@ public class GuiceAnnotationIntrospector extends NopAnnotationIntrospector
     }
     return Key.get(m.getGenericType(), guiceAnnotation);
   }
+
+  /**
+   * This method is used to find what property to ignore in deserialization. Jackson calls this method
+   * per every class and every constructor parameter.
+   *
+   * This implementation returns a {@link JsonIgnoreProperties.Value#empty()} that allows empty names if
+   * the parameters has the {@link JsonProperty} annotation. Otherwise, it returns
+   * {@code JsonIgnoreProperties.Value.forIgnoredProperties("")} that does NOT allow empty names.
+   * This behavior is to work around a bug in Jackson deserializer (see the below comment for details) and
+   * can be removed in the future after the bug is fixed.
+   * For example, suppose a constructor like below:
+   *
+   * <pre>{@code
+   * @JsonCreator
+   * public ClassWithJacksonInject(
+   *   @JsonProperty("val") String val,
+   *   @JacksonInject InjectedParameter injected
+   * )
+   * }</pre>
+   *
+   * During deserializing a JSON string into this class, this method will be called at least twice,
+   * one for {@code val} and another for {@code injected}. It will return {@code Value.empty()} for {@code val},
+   * while {Value.forIgnoredProperties("")} for {@code injected} because the later does not have {@code JsonProperty}.
+   * As a result, {@code injected} will be ignored during deserialization since it has no name.
+   */
+  @Override
+  public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac)
+  {
+    // We should not allow empty names in any case. However, there is a known bug in Jackson deserializer
+    // with ignorals (_arrayDelegateDeserializer is not copied when creating a contextual deserializer.
+    // See https://github.com/FasterXML/jackson-databind/issues/3022 for more details), which makes array
+    // deserialization failed even when the array is a valid field. To work around this bug, we return
+    // an empty ignoral when the given Annotated is a parameter with JsonProperty that needs to be deserialized.
+    // This is valid because every property with JsonProperty annoation should have a non-empty name.
+    // We can simply remove the below check after the Jackson bug is fixed.
+    //
+    // This check should be fine for so-called delegate creators that have only one argument without
+    // JsonProperty annotation, because this method is not even called for the argument of
+    // delegate creators. I'm not 100% sure why it's not called, but guess it's because the argument
+    // is some Java type that Jackson already knows how to deserialize. Since there is only one argument,
+    // Jackson perhaps is able to just deserialize it without introspection.
+    if (ac instanceof AnnotatedParameter) {
+      final AnnotatedParameter ap = (AnnotatedParameter) ac;
+      if (ap.hasAnnotation(JsonProperty.class)) {
+        return JsonIgnoreProperties.Value.empty();
+      }
+    }
+
+    return JsonIgnoreProperties.Value.forIgnoredProperties("");
+  }
 }
diff --git a/core/src/test/java/org/apache/druid/data/input/impl/DimensionsSpecSerdeTest.java b/core/src/test/java/org/apache/druid/data/input/impl/DimensionsSpecSerdeTest.java
index 45bb915..9b9b90a 100644
--- a/core/src/test/java/org/apache/druid/data/input/impl/DimensionsSpecSerdeTest.java
+++ b/core/src/test/java/org/apache/druid/data/input/impl/DimensionsSpecSerdeTest.java
@@ -19,9 +19,12 @@
 
 package org.apache.druid.data.input.impl;
 
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import junit.framework.Assert;
+import org.apache.druid.guice.DruidSecondaryModule;
+import org.apache.druid.guice.GuiceAnnotationIntrospector;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -34,6 +37,8 @@ public class DimensionsSpecSerdeTest
   private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
   static {
+    AnnotationIntrospector introspector = new GuiceAnnotationIntrospector();
+    DruidSecondaryModule.setupAnnotationIntrospector(OBJECT_MAPPER, introspector);
     OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
   }
 
diff --git a/core/src/test/java/org/apache/druid/data/input/impl/StringDimensionSchemaTest.java b/core/src/test/java/org/apache/druid/data/input/impl/StringDimensionSchemaTest.java
new file mode 100644
index 0000000..ad29097
--- /dev/null
+++ b/core/src/test/java/org/apache/druid/data/input/impl/StringDimensionSchemaTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.data.input.impl;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.druid.data.input.impl.DimensionSchema.MultiValueHandling;
+import org.apache.druid.guice.DruidSecondaryModule;
+import org.apache.druid.guice.GuiceAnnotationIntrospector;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class StringDimensionSchemaTest
+{
+  private final ObjectMapper jsonMapper;
+
+  public StringDimensionSchemaTest()
+  {
+    jsonMapper = new ObjectMapper();
+    AnnotationIntrospector introspector = new GuiceAnnotationIntrospector();
+    DruidSecondaryModule.setupAnnotationIntrospector(jsonMapper, introspector);
+    jsonMapper.registerSubtypes(StringDimensionSchema.class);
+  }
+
+  @Test
+  public void testDeserializeFromSimpleString() throws JsonProcessingException
+  {
+    final String json = "\"dim\"";
+    final StringDimensionSchema schema = (StringDimensionSchema) jsonMapper.readValue(json, DimensionSchema.class);
+    Assert.assertEquals(new StringDimensionSchema("dim"), schema);
+  }
+
+  @Test
+  public void testDeserializeFromJson() throws JsonProcessingException
+  {
+    final String json = "{\n"
+                        + "  \"type\" : \"StringDimensionSchema\",\n"
+                        + "  \"name\" : \"dim\",\n"
+                        + "  \"multiValueHandling\" : \"SORTED_SET\",\n"
+                        + "  \"createBitmapIndex\" : false\n"
+                        + "}";
+    final StringDimensionSchema schema = (StringDimensionSchema) jsonMapper.readValue(json, DimensionSchema.class);
+    Assert.assertEquals(new StringDimensionSchema("dim", MultiValueHandling.SORTED_SET, false), schema);
+  }
+}
diff --git a/core/src/test/java/org/apache/druid/guice/DruidSecondaryModuleTest.java b/core/src/test/java/org/apache/druid/guice/DruidSecondaryModuleTest.java
new file mode 100644
index 0000000..12ab2dd
--- /dev/null
+++ b/core/src/test/java/org/apache/druid/guice/DruidSecondaryModuleTest.java
@@ -0,0 +1,353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.guice;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+
+import javax.annotation.Nullable;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import java.util.List;
+import java.util.Properties;
+
+@RunWith(Enclosed.class)
+public class DruidSecondaryModuleTest
+{
+  private static final String PROPERTY_NAME = "druid.injected.val";
+  private static final String PROPERTY_VALUE = "this is the legit val";
+
+  public static class ConstructorWithJacksonInjectTest
+  {
+    @Test
+    public void testInjectWithAnEmptyPropertyNotOverrideInjection() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+      props.setProperty(PROPERTY_NAME, PROPERTY_VALUE);
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "{\"test\": \"this is an injection test\", \"\": \"nice try\" }";
+      final ClassWithJacksonInject object = mapper.readValue(json, ClassWithJacksonInject.class);
+      Assert.assertEquals("this is an injection test", object.test);
+      Assert.assertEquals(PROPERTY_VALUE, object.injected.val);
+    }
+
+    @Test
+    public void testInjectNormal() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+      props.setProperty(PROPERTY_NAME, PROPERTY_VALUE);
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "{\"test\": \"this is an injection test\" }";
+      final ClassWithJacksonInject object = mapper.readValue(json, ClassWithJacksonInject.class);
+      Assert.assertEquals("this is an injection test", object.test);
+      Assert.assertEquals(PROPERTY_VALUE, object.injected.val);
+    }
+
+    @Test
+    public void testInjectClassWithEmptyProperty() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+      props.setProperty(PROPERTY_NAME, PROPERTY_VALUE);
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "{\"test\": \"this is an injection test\", \"\": \"nice try\" }";
+      final ClassWithEmptyProperty object = mapper.readValue(json, ClassWithEmptyProperty.class);
+      Assert.assertEquals("this is an injection test", object.test);
+      Assert.assertEquals(PROPERTY_VALUE, object.injected.val);
+    }
+
+    private static class ClassWithJacksonInject
+    {
+      private final String test;
+
+      private InjectedParameter injected;
+
+      @JsonCreator
+      public ClassWithJacksonInject(
+          @JsonProperty("test") String test,
+          @JacksonInject InjectedParameter injected
+      )
+      {
+        this.test = test;
+        this.injected = injected;
+      }
+
+      @JsonProperty
+      public String getTest()
+      {
+        return test;
+      }
+    }
+
+    private static class ClassWithEmptyProperty
+    {
+      private final String test;
+
+      private InjectedParameter injected;
+
+      @JsonCreator
+      public ClassWithEmptyProperty(
+          @JsonProperty("test") String test,
+          @JacksonInject @JsonProperty("") InjectedParameter injected
+      )
+      {
+        this.test = test;
+        this.injected = injected;
+      }
+
+      @JsonProperty
+      public String getTest()
+      {
+        return test;
+      }
+    }
+  }
+
+  public static class ConstructorWithoutJacksonInjectTest
+  {
+    @Test
+    public void testInjectionWithEmptyPropertyName() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "[\"this is\", \"an injection test\"]";
+      final ClassWithConstructorOfEmptyName object = mapper.readValue(json, ClassWithConstructorOfEmptyName.class);
+      Assert.assertEquals(ImmutableList.of("this is", "an injection test"), object.getTest());
+    }
+
+    @Test
+    public void testInjectEmptyListWithEmptyPropertyName() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "[]";
+      final ClassWithConstructorOfEmptyName object = mapper.readValue(json, ClassWithConstructorOfEmptyName.class);
+      Assert.assertEquals(ImmutableList.of(), object.getTest());
+    }
+
+    @Test
+    public void testInjectClassWithFactoryMethodOfEmptyPropertyName() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "[\"this is\", \"an injection test\"]";
+      final ClassWithFactoryMethodOfEmptyName object = mapper.readValue(json, ClassWithFactoryMethodOfEmptyName.class);
+      Assert.assertEquals(ImmutableList.of("this is", "an injection test"), object.getTest());
+    }
+
+    @Test
+    public void testInjectEmptyListToClassWithFactoryMethodOfEmptyPropertyName() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "[]";
+      final ClassWithFactoryMethodOfEmptyName object = mapper.readValue(json, ClassWithFactoryMethodOfEmptyName.class);
+      Assert.assertEquals(ImmutableList.of(), object.getTest());
+    }
+
+    @Test
+    public void testInjectClassOfEmptyConstructor() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+      props.setProperty(PROPERTY_NAME, PROPERTY_VALUE);
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "{}";
+      final ClassOfEmptyConstructor object = mapper.readValue(json, ClassOfEmptyConstructor.class);
+      Assert.assertEquals("empty constructor", object.val);
+    }
+
+    private static class ClassWithConstructorOfEmptyName
+    {
+      private final List<String> test;
+
+      @JsonCreator
+      public ClassWithConstructorOfEmptyName(List<String> test)
+      {
+        this.test = test;
+      }
+
+      @JsonValue
+      public List<String> getTest()
+      {
+        return test;
+      }
+    }
+
+    private static class ClassWithFactoryMethodOfEmptyName
+    {
+      private final List<String> test;
+
+      @JsonCreator
+      public static ClassWithFactoryMethodOfEmptyName create(List<String> test)
+      {
+        return new ClassWithFactoryMethodOfEmptyName(test);
+      }
+
+      private ClassWithFactoryMethodOfEmptyName(List<String> test)
+      {
+        this.test = test;
+      }
+
+      @JsonValue
+      public List<String> getTest()
+      {
+        return test;
+      }
+    }
+
+    private static class ClassOfEmptyConstructor
+    {
+      private final String val;
+
+      @JsonCreator
+      public ClassOfEmptyConstructor()
+      {
+        this.val = "empty constructor";
+      }
+    }
+  }
+
+  public static class ClassOfMultipleJsonCreatorsTest
+  {
+    @Test
+    public void testDeserializeUsingMultiArgumentsConstructor() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+      props.setProperty(PROPERTY_NAME, PROPERTY_VALUE);
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "{\"val\": \"this is an injection test\", \"valLen\": 5, \"\": \"nice try\" }";
+      final ClassOfMultipleJsonCreators object = mapper.readValue(json, ClassOfMultipleJsonCreators.class);
+      Assert.assertEquals("this is an injection test", object.val);
+      Assert.assertEquals(5, object.valLen);
+      Assert.assertNotNull(object.injected);
+      Assert.assertEquals(PROPERTY_VALUE, object.injected.val);
+    }
+
+    @Test
+    public void testDeserializeUsingDelegateConstructor() throws JsonProcessingException
+    {
+      final Properties props = new Properties();
+      props.setProperty(PROPERTY_NAME, PROPERTY_VALUE);
+
+      final Injector injector = makeInjectorWithProperties(props);
+      final ObjectMapper mapper = makeObjectMapper(injector);
+      final String json = "\"this is an injection test\"";
+      final ClassOfMultipleJsonCreators object = mapper.readValue(json, ClassOfMultipleJsonCreators.class);
+      Assert.assertEquals("this is an injection test", object.val);
+      Assert.assertEquals(object.val.length(), object.valLen);
+      Assert.assertNull(object.injected);
+    }
+
+    private static class ClassOfMultipleJsonCreators
+    {
+      private final String val;
+      private final int valLen;
+      @Nullable
+      private final InjectedParameter injected;
+
+      @JsonCreator
+      public ClassOfMultipleJsonCreators(
+          @JsonProperty("val") String val,
+          @JsonProperty("valLen") int valLen,
+          @JacksonInject @Nullable InjectedParameter injected
+      )
+      {
+        this.val = val;
+        this.valLen = valLen;
+        this.injected = injected;
+      }
+
+      @JsonCreator
+      public static ClassOfMultipleJsonCreators create(String val)
+      {
+        return new ClassOfMultipleJsonCreators(val, val.length(), null);
+      }
+
+      @JsonProperty
+      public String getVal()
+      {
+        return val;
+      }
+
+      @JsonProperty
+      public int getValLen()
+      {
+        return valLen;
+      }
+    }
+  }
+
+  private static class InjectedParameter
+  {
+    @JsonProperty
+    private String val;
+  }
+
+  private static Injector makeInjectorWithProperties(final Properties props)
+  {
+    return Guice.createInjector(
+        ImmutableList.of(
+            new DruidGuiceExtensions(),
+            (Module) binder -> {
+              binder.bind(Validator.class).toInstance(Validation.buildDefaultValidatorFactory().getValidator());
+              binder.bind(JsonConfigurator.class).in(LazySingleton.class);
+              binder.bind(Properties.class).toInstance(props);
+              JsonConfigProvider.bind(binder, "druid.injected", InjectedParameter.class);
+            }
+        )
+    );
+  }
+
+  private static ObjectMapper makeObjectMapper(Injector injector)
+  {
+    final ObjectMapper mapper = new ObjectMapper();
+    DruidSecondaryModule.setupJackson(injector, mapper);
+    return mapper;
+  }
+}
diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/TestUtils.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/TestUtils.java
index c36a072..fe8cadf 100644
--- a/indexing-service/src/test/java/org/apache/druid/indexing/common/TestUtils.java
+++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/TestUtils.java
@@ -30,6 +30,7 @@ import org.apache.druid.client.indexing.IndexingServiceClient;
 import org.apache.druid.client.indexing.NoopIndexingServiceClient;
 import org.apache.druid.data.input.impl.NoopInputFormat;
 import org.apache.druid.data.input.impl.NoopInputSource;
+import org.apache.druid.guice.DruidSecondaryModule;
 import org.apache.druid.guice.FirehoseModule;
 import org.apache.druid.indexing.common.stats.DropwizardRowIngestionMetersFactory;
 import org.apache.druid.indexing.common.task.IndexTaskClientFactory;
@@ -43,6 +44,7 @@ import org.apache.druid.math.expr.ExprMacroTable;
 import org.apache.druid.query.expression.LookupEnabledTestExprMacroTable;
 import org.apache.druid.segment.IndexIO;
 import org.apache.druid.segment.IndexMergerV9;
+import org.apache.druid.segment.TestHelper;
 import org.apache.druid.segment.incremental.RowIngestionMetersFactory;
 import org.apache.druid.segment.loading.LocalDataSegmentPuller;
 import org.apache.druid.segment.loading.LocalLoadSpec;
@@ -115,6 +117,7 @@ public class TestUtils
           }
         }
     );
+    DruidSecondaryModule.setupAnnotationIntrospector(jsonMapper, TestHelper.makeAnnotationIntrospector());
 
     List<? extends Module> firehoseModules = new FirehoseModule().getJacksonModules();
     firehoseModules.forEach(jsonMapper::registerModule);
diff --git a/processing/src/test/java/org/apache/druid/segment/TestHelper.java b/processing/src/test/java/org/apache/druid/segment/TestHelper.java
index a6eb8b0..835d8b0 100644
--- a/processing/src/test/java/org/apache/druid/segment/TestHelper.java
+++ b/processing/src/test/java/org/apache/druid/segment/TestHelper.java
@@ -19,12 +19,16 @@
 
 package org.apache.druid.segment;
 
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
 import com.fasterxml.jackson.databind.InjectableValues;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import org.apache.druid.data.input.MapBasedRow;
 import org.apache.druid.data.input.Row;
+import org.apache.druid.guice.DruidSecondaryModule;
+import org.apache.druid.guice.GuiceAnnotationIntrospector;
 import org.apache.druid.jackson.DefaultObjectMapper;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.java.util.common.guava.Sequence;
@@ -71,9 +75,26 @@ public class TestHelper
     return new IndexIO(JSON_MAPPER, columnConfig);
   }
 
+  public static AnnotationIntrospector makeAnnotationIntrospector()
+  {
+    // Prepare annotationIntrospector with similar logic, except skip Guice loading
+    // because most tests don't use Guice injection.
+    return new GuiceAnnotationIntrospector()
+    {
+      @Override
+      public Object findInjectableValueId(AnnotatedMember m)
+      {
+        return null;
+      }
+    };
+  }
+
   public static ObjectMapper makeJsonMapper()
   {
     final ObjectMapper mapper = new DefaultObjectMapper();
+    AnnotationIntrospector introspector = makeAnnotationIntrospector();
+    DruidSecondaryModule.setupAnnotationIntrospector(mapper, introspector);
+
     mapper.setInjectableValues(
         new InjectableValues.Std()
             .addValue(ExprMacroTable.class.getName(), TestExprMacroTable.INSTANCE)
@@ -86,6 +107,7 @@ public class TestHelper
   public static ObjectMapper makeSmileMapper()
   {
     final ObjectMapper mapper = new DefaultObjectMapper();
+    DruidSecondaryModule.setupAnnotationIntrospector(mapper, makeAnnotationIntrospector());
     mapper.setInjectableValues(
         new InjectableValues.Std()
             .addValue(ExprMacroTable.class.getName(), TestExprMacroTable.INSTANCE)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org