From 62a9aa2fb18b003bea26c1308bb60122a4b4bf4d Mon Sep 17 00:00:00 2001 From: Joe Barnett Date: Fri, 19 May 2023 15:47:41 -0700 Subject: [PATCH] Add guice7 (jakarta.inject) module Copy guice (javax.inject) module over to a new guice 7 (jakarta.inject) module. Replace javax imports with jakarta imports, and leave everything else the same. Fixes #206 --- README.md | 4 +- guice/pom.xml | 2 +- guice7/README.md | 106 ++++++++ guice7/pom.xml | 75 ++++++ .../guice7/GuiceAnnotationIntrospector.java | 114 +++++++++ .../module/guice7/GuiceInjectableValues.java | 22 ++ .../module/guice7/ObjectMapperModule.java | 156 ++++++++++++ .../module/guice7/PackageVersion.java.in | 20 ++ guice7/src/main/resources/META-INF/LICENSE | 8 + guice7/src/main/resources/META-INF/NOTICE | 20 ++ guice7/src/moditect/module-info.java | 14 ++ .../fasterxml/jackson/module/guice7/Ann.java | 18 ++ .../module/guice7/ExtendInjectableTest.java | 199 +++++++++++++++ .../module/guice7/ObjectMapperModuleTest.java | 226 ++++++++++++++++++ pom.xml | 1 + 15 files changed, 983 insertions(+), 2 deletions(-) create mode 100644 guice7/README.md create mode 100644 guice7/pom.xml create mode 100644 guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceAnnotationIntrospector.java create mode 100644 guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceInjectableValues.java create mode 100644 guice7/src/main/java/com/fasterxml/jackson/module/guice7/ObjectMapperModule.java create mode 100644 guice7/src/main/java/com/fasterxml/jackson/module/guice7/PackageVersion.java.in create mode 100644 guice7/src/main/resources/META-INF/LICENSE create mode 100644 guice7/src/main/resources/META-INF/NOTICE create mode 100644 guice7/src/moditect/module-info.java create mode 100644 guice7/src/test/java/com/fasterxml/jackson/module/guice7/Ann.java create mode 100644 guice7/src/test/java/com/fasterxml/jackson/module/guice7/ExtendInjectableTest.java create mode 100644 guice7/src/test/java/com/fasterxml/jackson/module/guice7/ObjectMapperModuleTest.java diff --git a/README.md b/README.md index de80c0d9d..2fad67c44 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ Currently included are: * [Afterburner](afterburner/) -- * [Blackbird](blackbird/) (NEW in 2.12 -- to eventually replace Afterburner) -* [Guice](guice/) +* Guice + * "Old" (`javax.inject`) based versions: [Guice](guice/) + * New "Jakarta" (`jakarta.inject`) based versions: [Guice 7](guice7/) (added in 2.16) * Java XML Binding Annotation compatibility * "Old" (`java.xml.bind`) annotations: [JAXB Annotations](jaxb/) * New "Jakarta" (`jakarta.xml.bind`): [Jakarta XML Bind Annotations](jakarta-xmlbind/) (added in 2.13) diff --git a/guice/pom.xml b/guice/pom.xml index 3e7c30ba3..215a02bd4 100644 --- a/guice/pom.xml +++ b/guice/pom.xml @@ -12,7 +12,7 @@ 2.16.0-SNAPSHOT jackson-module-guice - Jackson module: Guice + Jackson module: Guice (javax.inject) bundle Stuff to make integration with Guice a bit easier diff --git a/guice7/README.md b/guice7/README.md new file mode 100644 index 000000000..e15e4b775 --- /dev/null +++ b/guice7/README.md @@ -0,0 +1,106 @@ +# jackson-module-guice7 + +## Documentation + +This is a copy of jackson-module-guice that works with guice version 7, using the jakarta.inject namespace. + +This extension allows Jackson to delegate ObjectMapper creation and value injection to Guice when handling data bindings. +Using the ObjectMapperModule you can register Jackson data binding modules like so: + +~~~~~ + + Injector injector = Guice.createInjector( + new ObjectMapperModule().registerModule(new IntegerAsBase16Module()) + ); + + + public class IntegerAsBase16Module extends SimpleModule + { + public IntegerAsBase16Module() { + super("IntegerAsBase16"); + + addSerializer( Integer.class, + new JsonSerializer() { + @Override + public void serialize( Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider ) + throws IOException, JsonProcessingException + { + jsonGenerator.writeString(new BigInteger(String.valueOf(integer)).toString(16).toUpperCase()); + } + } + ); + } + } + +~~~~~ + +Subsequently, the ObjectMapper, created from the Guice injector above, will apply the proper data bindings to serialize +Integers as base 16 strings: + +~~~~~ + + mapper.writeValueAsString(new Integer(10)) ==> "A" + +~~~~~ + +Additional Guice Modules can be used when creating the Injector to automatically inject values into value objects +being de-serialized. The @JacksonInject annotation can be used to trigger Guice driven injection. + +Here's an example of a value object where Guice injects three of the members on behalf of Jackson. The first +uses the @JacksonInject annotation, the second uses @JacksonInject with a specific Named binding, and the +third uses @JacksonInject combined with another annotation (@Ann). + +~~~~~ + + public class SomeBean { + @JacksonInject + private int one; + + @JacksonInject + @Named("two") + private int two; + + @JacksonInject + @Ann + private int three; + + @JsonProperty + private int four; + + public boolean verify() { + Assert.assertEquals(1, one); + Assert.assertEquals(2, two); + Assert.assertEquals(3, three); + Assert.assertEquals(4, four); + return true; + } + } + +~~~~~ + +The last, the fourth field, annotated with @JsonProperty uses standard ObjectMapper behavior unlike the other three +which are injected by Guice. The following code snippet demonstrates Guice injection leading to a true return on the +verify() method: + + +~~~~~ + + final Injector injector = Guice.createInjector( + new ObjectMapperModule(), + new Module() + { + @Override + public void configure(Binder binder) + { + binder.bind(Integer.class).toInstance(1); + binder.bind(Integer.class).annotatedWith(Names.named("two")).toInstance(2); + binder.bind(Integer.class).annotatedWith(Ann.class).toInstance(3); + } + } + ); + + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + mapper.readValue("{\"four\": 4}", SomeBean.class).verify(); + +~~~~~ + diff --git a/guice7/pom.xml b/guice7/pom.xml new file mode 100644 index 000000000..729520a58 --- /dev/null +++ b/guice7/pom.xml @@ -0,0 +1,75 @@ + + + + + + + + 4.0.0 + + com.fasterxml.jackson.module + jackson-modules-base + 2.16.0-SNAPSHOT + + jackson-module-guice7 + Jackson module: Guice 7+ (jakarta.inject) + bundle + + Stuff to make integration with Guice 7+ a bit easier + https://github.com/FasterXML/jackson-modules-base + + + + The Apache Software License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 7.0.0 + + + com/fasterxml/jackson/module/guice7 + ${project.groupId}.guice7 + + + + + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.google.inject + guice + ${version.guice} + + + + + + + com.google.code.maven-replacer-plugin + replacer + + + + org.moditect + moditect-maven-plugin + + + + + diff --git a/guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceAnnotationIntrospector.java b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceAnnotationIntrospector.java new file mode 100644 index 000000000..ce535e2a7 --- /dev/null +++ b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceAnnotationIntrospector.java @@ -0,0 +1,114 @@ +package com.fasterxml.jackson.module.guice; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.databind.introspect.*; +import com.google.inject.BindingAnnotation; +import com.google.inject.Key; + +import jakarta.inject.Qualifier; +import java.lang.annotation.Annotation; +import java.util.Arrays; + +public class GuiceAnnotationIntrospector extends NopAnnotationIntrospector +{ + private static final long serialVersionUID = 1L; + + @Override // since 2.9 + public JacksonInject.Value findInjectableValue(AnnotatedMember m) { + Object id = _findGuiceInjectId(m); + if (id == null) { + return null; + } + return JacksonInject.Value.forId(id); + } + + @Deprecated // since 2.9 + @Override + public Object findInjectableValueId(AnnotatedMember m) { + return _findGuiceInjectId(m); + } + + private Object _findGuiceInjectId(AnnotatedMember m) + { + /* + * We check on three kinds of annotations: @JacksonInject for types + * that were actually created for Jackson, and @Inject (both Guice's + * and jakarta.inject) for types that (for example) extend already + * annotated objects. + * + * Postel's law: http://en.wikipedia.org/wiki/Robustness_principle + */ + // 19-Apr-2017, tatu: Actually this is something that should not be done; + // instead, pair of AnnotationIntrospector should be used... Leaving in + // for now, however. + if ((m.getAnnotation(JacksonInject.class) == null) && + (m.getAnnotation(jakarta.inject.Inject.class) == null) && + (m.getAnnotation(com.google.inject.Inject.class) == null)) + { + return null; + } + + final AnnotatedMember guiceMember; + final Annotation guiceAnnotation; + + if ((m instanceof AnnotatedField) || (m instanceof AnnotatedParameter)) { + /* On fields and parameters the @Qualifier annotation and type to + * inject are the member itself, so, nothing to do here... + */ + guiceMember = m; + AnnotationMap anns = ((AnnotatedMember) m).getAllAnnotations(); + guiceAnnotation = findBindingAnnotation(anns.annotations()); + } else if (m instanceof AnnotatedMethod) { + /* For method injection, the @Qualifier and type to inject are + * specified on the parameter. Here, we only consider methods with + * a single parameter. + */ + final AnnotatedMethod a = (AnnotatedMethod) m; + if (a.getParameterCount() != 1) { + return null; + } + + /* Jackson does not *YET* give us parameter annotations on methods, + * only on constructors, henceforth we have to do a bit of work + * ourselves! + */ + guiceMember = a.getParameter(0); + final Annotation[] annotations = a.getMember().getParameterAnnotations()[0]; + guiceAnnotation = findBindingAnnotation(Arrays.asList(annotations)); + } else { + /* Ignore constructors */ + return null; + } + + /* Depending on whether we have an annotation (or not) return the + * correct Guice key that Jackson will use to query the Injector. + */ + if (guiceAnnotation == null) { + // 19-Sep-2016, tatu: Used to pass `getGenericType()`, but that is now deprecated. + // Looking at code in Guice Key, I don't think it does particularly good job + // in resolving generic types, so this is probably safe... +// return Key.get(guiceMember.getGenericType()); + return Key.get((java.lang.reflect.Type) guiceMember.getRawType()); + } +// return Key.get(guiceMember.getGenericType(), guiceAnnotation); + return Key.get((java.lang.reflect.Type) guiceMember.getRawType(), guiceAnnotation); + } + + /* + * We want to figure out if a @BindingAnnotation or @Qualifier + * annotation are present on what we're trying to inject. + * Those annotations are only possible on fields or parameters. + */ + private Annotation findBindingAnnotation(Iterable annotations) + { + for (Annotation annotation : annotations) { + // Check on guice (BindingAnnotation) & jakarta (Qualifier) based injections + if (annotation.annotationType().isAnnotationPresent(BindingAnnotation.class) || + annotation.annotationType().isAnnotationPresent(Qualifier.class)) + { + return annotation; + } + } + return null; + } +} diff --git a/guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceInjectableValues.java b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceInjectableValues.java new file mode 100644 index 000000000..5080abe44 --- /dev/null +++ b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/GuiceInjectableValues.java @@ -0,0 +1,22 @@ +package com.fasterxml.jackson.module.guice; + +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.InjectableValues; +import com.google.inject.Injector; +import com.google.inject.Key; + +public class GuiceInjectableValues extends InjectableValues +{ + private final Injector injector; + + public GuiceInjectableValues(Injector injector) {this.injector = injector;} + + @Override + public Object findInjectableValue( + Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance + ) + { + return injector.getInstance((Key) valueId); + } +} diff --git a/guice7/src/main/java/com/fasterxml/jackson/module/guice7/ObjectMapperModule.java b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/ObjectMapperModule.java new file mode 100644 index 000000000..2a1a85c2a --- /dev/null +++ b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/ObjectMapperModule.java @@ -0,0 +1,156 @@ +package com.fasterxml.jackson.module.guice; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair; +import com.google.inject.Binder; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.binder.ScopedBindingBuilder; + +public class ObjectMapperModule implements com.google.inject.Module +{ + private final List modulesToAdd = new ArrayList(); + private final List> modulesToInject = new ArrayList>(); + private final Key objectMapperKey; + + // @since 2.8 + private ObjectMapper objectMapper; + + private Class scope = null; + + public ObjectMapperModule() + { + this(Key.get(ObjectMapper.class)); + } + + public ObjectMapperModule(Class annotation) + { + this(Key.get(ObjectMapper.class, annotation)); + } + + public ObjectMapperModule(Annotation annotation) + { + this(Key.get(ObjectMapper.class, annotation)); + } + + public ObjectMapperModule(Key objectMapperKey) + { + this.objectMapperKey = objectMapperKey; + } + + public ObjectMapperModule in(Class scopeAnnotation) + { + scope = scopeAnnotation; + return this; + } + + public ObjectMapperModule registerModule(Module module) + { + modulesToAdd.add(module); + return this; + } + + public ObjectMapperModule registerModule(Class clazz) + { + return registerModule(Key.get(clazz)); + } + + public ObjectMapperModule registerModule(Class clazz, Class annotation) + { + return registerModule(Key.get(clazz, annotation)); + } + + public ObjectMapperModule registerModule(Class clazz, Annotation annotation) + { + return registerModule(Key.get(clazz, annotation)); + } + + public ObjectMapperModule registerModule(Key key) + { + modulesToInject.add(key); + return this; + } + + /** + * @param m ObjectMapper to use for newly constructed module + * + * @since 2.8 + */ + public ObjectMapperModule withObjectMapper(ObjectMapper m) + { + objectMapper = m; + return this; + } + + @Override + public void configure(Binder binder) + { + final ScopedBindingBuilder builder = binder.bind(objectMapperKey) + .toProvider(new ObjectMapperProvider(modulesToInject, modulesToAdd, objectMapper)); + + if (scope != null) { + builder.in(scope); + } + } + + private static class ObjectMapperProvider implements Provider + { + private final List> modulesToInject; + private final List modulesToAdd; + + private final List> providedModules; + private Injector injector; + private final ObjectMapper objectMapper; + + public ObjectMapperProvider(List> modulesToInject, + List modulesToAdd, ObjectMapper mapper) + { + this.modulesToInject = modulesToInject; + this.modulesToAdd = modulesToAdd; + objectMapper = mapper; + this.providedModules = new ArrayList>(); + } + + @Inject + public void configure(Injector inj) { + injector = inj; + for (Key key : modulesToInject) { + providedModules.add(injector.getProvider(key)); + } + } + + @Override + public ObjectMapper get() + { + ObjectMapper mapper = objectMapper; + if (mapper == null) { + mapper = new ObjectMapper(); + } else { + mapper = mapper.copy(); + } + mapper.registerModules(modulesToAdd); + for (Provider provider : providedModules) { + mapper.registerModule(provider.get()); + } + + final GuiceAnnotationIntrospector guiceIntrospector = new GuiceAnnotationIntrospector(); + mapper.setInjectableValues(new GuiceInjectableValues(injector)); + mapper.setAnnotationIntrospectors( + new AnnotationIntrospectorPair( + guiceIntrospector, mapper.getSerializationConfig().getAnnotationIntrospector() + ), + new AnnotationIntrospectorPair( + guiceIntrospector, mapper.getDeserializationConfig().getAnnotationIntrospector() + ) + ); + return mapper; + } + } +} diff --git a/guice7/src/main/java/com/fasterxml/jackson/module/guice7/PackageVersion.java.in b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/PackageVersion.java.in new file mode 100644 index 000000000..7860aa14b --- /dev/null +++ b/guice7/src/main/java/com/fasterxml/jackson/module/guice7/PackageVersion.java.in @@ -0,0 +1,20 @@ +package @package@; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.Versioned; +import com.fasterxml.jackson.core.util.VersionUtil; + +/** + * Automatically generated from PackageVersion.java.in during + * packageVersion-generate execution of maven-replacer-plugin in + * pom.xml. + */ +public final class PackageVersion implements Versioned { + public final static Version VERSION = VersionUtil.parseVersion( + "@projectversion@", "@projectgroupid@", "@projectartifactid@"); + + @Override + public Version version() { + return VERSION; + } +} diff --git a/guice7/src/main/resources/META-INF/LICENSE b/guice7/src/main/resources/META-INF/LICENSE new file mode 100644 index 000000000..a9e546210 --- /dev/null +++ b/guice7/src/main/resources/META-INF/LICENSE @@ -0,0 +1,8 @@ +This copy of Jackson JSON processor `jackson-module-guice` module is licensed under the +Apache (Software) License, version 2.0 ("the License"). +See the License for details about distribution rights, and the +specific rights regarding derivative works. + +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 diff --git a/guice7/src/main/resources/META-INF/NOTICE b/guice7/src/main/resources/META-INF/NOTICE new file mode 100644 index 000000000..4c976b7b4 --- /dev/null +++ b/guice7/src/main/resources/META-INF/NOTICE @@ -0,0 +1,20 @@ +# Jackson JSON processor + +Jackson is a high-performance, Free/Open Source JSON processing library. +It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has +been in development since 2007. +It is currently developed by a community of developers, as well as supported +commercially by FasterXML.com. + +## Licensing + +Jackson core and extension components may licensed under different licenses. +To find the details that apply to this artifact see the accompanying LICENSE file. +For more information, including possible other licensing options, contact +FasterXML.com (http://fasterxml.com). + +## Credits + +A list of contributors may be found from CREDITS file, which is included +in some artifacts (usually source distributions); but is always available +from the source code management (SCM) system project uses. diff --git a/guice7/src/moditect/module-info.java b/guice7/src/moditect/module-info.java new file mode 100644 index 000000000..8785687cc --- /dev/null +++ b/guice7/src/moditect/module-info.java @@ -0,0 +1,14 @@ +module com.fasterxml.jackson.module.guice7 { + //Jakarta Reference Implementation + requires static jakarta.inject; + + requires com.fasterxml.jackson.annotation; + requires com.fasterxml.jackson.core; + requires com.fasterxml.jackson.databind; + + requires com.google.guice; + + exports com.fasterxml.jackson.module.guice7; + + //NOTE : Don't auto provide jackson guice module as some may want to bind their own ObjectMapper? +} diff --git a/guice7/src/test/java/com/fasterxml/jackson/module/guice7/Ann.java b/guice7/src/test/java/com/fasterxml/jackson/module/guice7/Ann.java new file mode 100644 index 000000000..8b9dc1cc1 --- /dev/null +++ b/guice7/src/test/java/com/fasterxml/jackson/module/guice7/Ann.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.module.guice; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + */ +@Retention(RUNTIME) +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@BindingAnnotation +public @interface Ann +{ +} diff --git a/guice7/src/test/java/com/fasterxml/jackson/module/guice7/ExtendInjectableTest.java b/guice7/src/test/java/com/fasterxml/jackson/module/guice7/ExtendInjectableTest.java new file mode 100644 index 000000000..dd81a922c --- /dev/null +++ b/guice7/src/test/java/com/fasterxml/jackson/module/guice7/ExtendInjectableTest.java @@ -0,0 +1,199 @@ +package com.fasterxml.jackson.module.guice; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Binder; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.name.Names; +import org.junit.Assert; +import org.junit.Test; + +import jakarta.inject.Inject; + +/** + */ +public class ExtendInjectableTest +{ + final ConstructorDependency constructorInjected = new ConstructorDependency(); + final ConstructorDependency constructorInjectedWithCustomAnnotation = new ConstructorDependency(); + final ConstructorDependency constructorInjectedWithGuiceAnnotation = new ConstructorDependency(); + final ConstructorDependency constructorInjectedWithJavaAnnotation = new ConstructorDependency(); + final FieldDependency fieldInjected = new FieldDependency(); + final FieldDependency fieldInjectedWithCustomAnnotation = new FieldDependency(); + final FieldDependency fieldInjectedWithGuiceAnnotation = new FieldDependency(); + final FieldDependency fieldInjectedWithJavaAnnotation = new FieldDependency(); + final MethodDependency methodInjected = new MethodDependency(); + final MethodDependency methodInjectedWithCustomAnnotation = new MethodDependency(); + final MethodDependency methodInjectedWithGuiceAnnotation = new MethodDependency(); + final MethodDependency methodInjectedWithJavaAnnotation = new MethodDependency(); + + @Test + public void testModulesRegisteredThroughInjectionWithKey() throws Exception + { + final Injector injector = Guice.createInjector(new ObjectMapperModule(), + new Module() { + @Override + public void configure(Binder binder) + { + binder.bind(ConstructorDependency.class).toInstance(constructorInjected); + binder.bind(ConstructorDependency.class).annotatedWith(Ann.class).toInstance(constructorInjectedWithCustomAnnotation); + binder.bind(ConstructorDependency.class).annotatedWith(Names.named("guice")).toInstance(constructorInjectedWithGuiceAnnotation); + binder.bind(ConstructorDependency.class).annotatedWith(Names.named("jakarta")).toInstance(constructorInjectedWithJavaAnnotation); + binder.bind(FieldDependency.class).toInstance(fieldInjected); + binder.bind(FieldDependency.class).annotatedWith(Ann.class).toInstance(fieldInjectedWithCustomAnnotation); + binder.bind(FieldDependency.class).annotatedWith(Names.named("guice")).toInstance(fieldInjectedWithGuiceAnnotation); + binder.bind(FieldDependency.class).annotatedWith(Names.named("jakarta")).toInstance(fieldInjectedWithJavaAnnotation); + binder.bind(MethodDependency.class).toInstance(methodInjected); + binder.bind(MethodDependency.class).annotatedWith(Ann.class).toInstance(methodInjectedWithCustomAnnotation); + binder.bind(MethodDependency.class).annotatedWith(Names.named("guice")).toInstance(methodInjectedWithGuiceAnnotation); + binder.bind(MethodDependency.class).annotatedWith(Names.named("jakarta")).toInstance(methodInjectedWithJavaAnnotation); + } + }); + + /* First of all, just get an InjectableBean out of guice to make sure it's correct (test the test) */ + verifyInjections("From Guice's Injector", injector.getInstance(InjectableBean.class)); + + /* Now let's try injections via our ObjectMapper (plus some values) */ + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + + final ReadableBean bean = mapper.readValue("{\"constructor_value\":\"myConstructor\",\"field_value\":\"myField\",\"method_value\":\"myMethod\"}", ReadableBean.class); + + Assert.assertEquals("myConstructor", bean.constructorValue); + Assert.assertEquals("myMethod", bean.methodValue); + Assert.assertEquals("myField", bean.fieldValue); + + verifyInjections("From Jackson's ObjectMapper", bean); + + } + + private void verifyInjections(String message, InjectableBean injected) + { + Assert.assertSame(message, constructorInjected, injected.constructorInjected); + Assert.assertSame(message, constructorInjectedWithJavaAnnotation, injected.constructorInjectedWithJavaAnnotation); + Assert.assertSame(message, constructorInjectedWithGuiceAnnotation, injected.constructorInjectedWithGuiceAnnotation); + Assert.assertSame(message, constructorInjectedWithCustomAnnotation, injected.constructorInjectedWithCustomAnnotation); + + Assert.assertSame(message, methodInjected, injected.methodInjected); + Assert.assertSame(message, methodInjectedWithJavaAnnotation, injected.methodInjectedWithJavaAnnotation); + Assert.assertSame(message, methodInjectedWithGuiceAnnotation, injected.methodInjectedWithGuiceAnnotation); + Assert.assertSame(message, methodInjectedWithCustomAnnotation, injected.methodInjectedWithCustomAnnotation); + + Assert.assertSame(message, fieldInjected, injected.fieldInjected); + Assert.assertSame(message, fieldInjectedWithJavaAnnotation, injected.fieldInjectedWithJavaAnnotation); + Assert.assertSame(message, fieldInjectedWithGuiceAnnotation, injected.fieldInjectedWithGuiceAnnotation); + Assert.assertSame(message, fieldInjectedWithCustomAnnotation, injected.fieldInjectedWithCustomAnnotation); + } + + /* ===================================================================== */ + /* SUPPORT CLASSES */ + /* ===================================================================== */ + + static class ReadableBean extends InjectableBean { + + @JsonProperty("field_value") + String fieldValue; + String methodValue; + final String constructorValue; + + @JsonCreator + private ReadableBean(@JacksonInject ConstructorDependency constructorInjected, + @JacksonInject @jakarta.inject.Named("jakarta") ConstructorDependency constructorInjectedWithJavaAnnotation, + @JacksonInject @com.google.inject.name.Named("guice") ConstructorDependency constructorInjectedWithGuiceAnnotation, + @JacksonInject @Ann ConstructorDependency constructorInjectedWithCustomAnnotation, + @JsonProperty("constructor_value") String constructorValue) + { + super(constructorInjected, + constructorInjectedWithJavaAnnotation, + constructorInjectedWithGuiceAnnotation, + constructorInjectedWithCustomAnnotation); + this.constructorValue = constructorValue; + } + + @JsonProperty("method_value") + public void setMethodValue(String methodValue) + { + this.methodValue = methodValue; + } + + } + + /* ===================================================================== */ + + static class ConstructorDependency {}; + static class MethodDependency {}; + static class FieldDependency {}; + + static class InjectableBean + { + @Inject + FieldDependency fieldInjected; + MethodDependency methodInjected; + final ConstructorDependency constructorInjected; + + @JacksonInject + @Inject + @jakarta.inject.Named("jakarta") + FieldDependency fieldInjectedWithJavaAnnotation; + MethodDependency methodInjectedWithJavaAnnotation; + final ConstructorDependency constructorInjectedWithJavaAnnotation; + + @JacksonInject + @Inject + @com.google.inject.name.Named("guice") + FieldDependency fieldInjectedWithGuiceAnnotation; + MethodDependency methodInjectedWithGuiceAnnotation; + final ConstructorDependency constructorInjectedWithGuiceAnnotation; + + @JacksonInject + @Inject + @Ann + FieldDependency fieldInjectedWithCustomAnnotation; + MethodDependency methodInjectedWithCustomAnnotation; + final ConstructorDependency constructorInjectedWithCustomAnnotation; + + @Inject // this is simply to make sure we *can* build this correctly + protected InjectableBean(ConstructorDependency constructorInjected, + @jakarta.inject.Named("jakarta") ConstructorDependency constructorInjectedWithJavaAnnotation, + @com.google.inject.name.Named("guice") ConstructorDependency constructorInjectedWithGuiceAnnotation, + @Ann ConstructorDependency constructorInjectedWithCustomAnnotation) + { + this.constructorInjected = constructorInjected; + this.constructorInjectedWithJavaAnnotation = constructorInjectedWithJavaAnnotation; + this.constructorInjectedWithGuiceAnnotation = constructorInjectedWithGuiceAnnotation; + this.constructorInjectedWithCustomAnnotation = constructorInjectedWithCustomAnnotation; + } + + @JacksonInject + @Inject // not annotated + private void inject1(MethodDependency dependency) + { + this.methodInjected = dependency; + } + + @JacksonInject + @Inject + private void inject2(@jakarta.inject.Named("jakarta") MethodDependency dependency) + { + this.methodInjectedWithJavaAnnotation = dependency; + } + + @JacksonInject + @Inject + private void inject3(@com.google.inject.name.Named("guice") MethodDependency dependency) + { + this.methodInjectedWithGuiceAnnotation = dependency; + } + + @JacksonInject + @Inject + private void inject4(@Ann MethodDependency dependency) + { + this.methodInjectedWithCustomAnnotation = dependency; + } + + } +} diff --git a/guice7/src/test/java/com/fasterxml/jackson/module/guice7/ObjectMapperModuleTest.java b/guice7/src/test/java/com/fasterxml/jackson/module/guice7/ObjectMapperModuleTest.java new file mode 100644 index 000000000..6520a7791 --- /dev/null +++ b/guice7/src/test/java/com/fasterxml/jackson/module/guice7/ObjectMapperModuleTest.java @@ -0,0 +1,226 @@ +package com.fasterxml.jackson.module.guice; + +import java.io.IOException; +import java.math.BigInteger; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonProperty; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; + +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import com.google.inject.Binder; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.name.Names; + +import org.junit.Assert; +import org.junit.Test; + +public class ObjectMapperModuleTest +{ + @Test + public void testJacksonInjectThroughGuice() throws Exception + { + final Injector injector = Guice.createInjector( + new ObjectMapperModule(), + new Module() + { + @Override + public void configure(Binder binder) + { + binder.bind(Integer.class).toInstance(1); + // guice based named injection + binder.bind(Integer.class).annotatedWith(Names.named("two")).toInstance(2); + binder.bind(Integer.class).annotatedWith(Ann.class).toInstance(3); + // jakarta based named injection + binder.bind(Integer.class).annotatedWith(Names.named("five")).toInstance(5); + // guice based method injection + binder.bind(Integer.class).annotatedWith(Names.named("six")).toInstance(6); + // jakarta based method injection + binder.bind(Integer.class).annotatedWith(Names.named("seven")).toInstance(7); + // test other method injections (need different keys, so use Long + binder.bind(Long.class).annotatedWith(Ann.class).toInstance(8L); + binder.bind(Long.class).toInstance(9L); + } + } + ); + + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + + mapper.readValue("{\"four\": 4}", SomeBean.class).verify(); + } + + @Test + public void testModulesRegisteredThroughNormalInstantiation() throws Exception + { + final Injector injector = Guice.createInjector( + new ObjectMapperModule().registerModule(new IntegerAsBase16Module()) + ); + + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + + Assert.assertEquals(mapper.writeValueAsString(new Integer(10)), "\"A\""); + } + + @Test + public void testModulesRegisteredThroughInjection() throws Exception + { + final Injector injector = Guice.createInjector( + new ObjectMapperModule().registerModule(IntegerAsBase16Module.class) + ); + + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + + Assert.assertEquals(mapper.writeValueAsString(new Integer(10)), "\"A\""); + } + + @Test + public void testModulesRegisteredThroughInjectionWithAnnotation() throws Exception + { + final Injector injector = Guice.createInjector( + new ObjectMapperModule().registerModule(IntegerAsBase16Module.class, Ann.class), + new Module() + { + @Override + public void configure(Binder binder) + { + binder.bind(IntegerAsBase16Module.class).annotatedWith(Ann.class).to(IntegerAsBase16Module.class); + } + } + ); + + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + + Assert.assertEquals(mapper.writeValueAsString(new Integer(10)), "\"A\""); + } + + @Test + public void testModulesRegisteredThroughInjectionWithNameAnnotation() throws Exception + { + final Injector injector = Guice.createInjector( + new ObjectMapperModule().registerModule(IntegerAsBase16Module.class, Names.named("billy")), + new Module() + { + @Override + public void configure(Binder binder) + { + binder.bind(IntegerAsBase16Module.class) + .annotatedWith(Names.named("billy")) + .to(IntegerAsBase16Module.class); + } + } + ); + + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + + Assert.assertEquals(mapper.writeValueAsString(new Integer(10)), "\"A\""); + } + + @Test + public void testModulesRegisteredThroughInjectionWithKey() throws Exception + { + final Injector injector = Guice.createInjector( + new ObjectMapperModule().registerModule(Key.get(IntegerAsBase16Module.class)) + ); + + final ObjectMapper mapper = injector.getInstance(ObjectMapper.class); + + Assert.assertEquals(mapper.writeValueAsString(new Integer(10)), "\"A\""); + } + + private static class SomeBean + { + @JacksonInject + private int one; + + @JacksonInject + @com.google.inject.name.Named("two") + private int two; + + @JacksonInject + @Ann + private int three; + + @JsonProperty + private int four; + + @JacksonInject + @jakarta.inject.Named("five") + private int five; + + // Those will be injected by methods + private int six; + private int seven; + private long eight; + private long nine; + + @JacksonInject + private void injectSix(@com.google.inject.name.Named("six") int s) + { + this.six = s; + } + + @JacksonInject + private void injectSeven(@jakarta.inject.Named("seven") int s) + { + this.seven = s; + } + + @JacksonInject + private void injectEight(@Ann long e) + { + this.eight = e; + } + + @JacksonInject + private void injectNine(long n) + { + this.nine = n; + } + + public boolean verify() + { + Assert.assertEquals(1, one); + Assert.assertEquals(2, two); + Assert.assertEquals(3, three); + Assert.assertEquals(4, four); + Assert.assertEquals(5, five); + Assert.assertEquals(6, six); + Assert.assertEquals(7, seven); + Assert.assertEquals(8, eight); + Assert.assertEquals(9, nine); + return true; + } + + } + + @SuppressWarnings("serial") + static class IntegerAsBase16Module extends SimpleModule + { + public IntegerAsBase16Module() { + super("IntegerAsBase16"); + + addSerializer( + Integer.class, + new JsonSerializer() + { + @Override + public void serialize( + Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider + ) throws IOException, JsonProcessingException + { + jsonGenerator.writeString(new BigInteger(String.valueOf(integer)).toString(16).toUpperCase()); + } + } + ); + } + } +} diff --git a/pom.xml b/pom.xml index f98edf7c1..c19c0a5ab 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ not datatype, data format, or JAX-RS provider modules. afterburner blackbird guice + guice7 jakarta-xmlbind jaxb mrbean