Skip to content

Commit

Permalink
Merge pull request #209 from josephlbarnett/guice7
Browse files Browse the repository at this point in the history
Add guice7 (jakarta.inject) module
  • Loading branch information
cowtowncoder committed May 22, 2023
2 parents c10dc50 + 62a9aa2 commit 1794b41
Show file tree
Hide file tree
Showing 15 changed files with 983 additions and 2 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion guice/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<version>2.16.0-SNAPSHOT</version>
</parent>
<artifactId>jackson-module-guice</artifactId>
<name>Jackson module: Guice</name>
<name>Jackson module: Guice (javax.inject)</name>
<packaging>bundle</packaging>

<description>Stuff to make integration with Guice a bit easier</description>
Expand Down
106 changes: 106 additions & 0 deletions guice7/README.md
Original file line number Diff line number Diff line change
@@ -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<Integer>() {
@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();
~~~~~

75 changes: 75 additions & 0 deletions guice7/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-modules-base</artifactId>
<version>2.16.0-SNAPSHOT</version>
</parent>
<artifactId>jackson-module-guice7</artifactId>
<name>Jackson module: Guice 7+ (jakarta.inject)</name>
<packaging>bundle</packaging>

<description>Stuff to make integration with Guice 7+ a bit easier</description>
<url>https://github.com/FasterXML/jackson-modules-base</url>

<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>

<properties>
<version.guice>7.0.0</version.guice>

<!-- Generate PackageVersion.java into this directory. -->
<packageVersion.dir>com/fasterxml/jackson/module/guice7</packageVersion.dir>
<packageVersion.package>${project.groupId}.guice7</packageVersion.package>
<!-- default OSGi imports, exports should work fine -->
</properties>

<dependencies>
<!-- Extends Jackson mapper, but also uses types from core, hence direct dep as well -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>${version.guice}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
</plugin>
<!-- 14-Mar-2019, tatu: Add rudimentary JDK9+ module info. To build with JDK 8
will have to use `moduleInfoFile` as anything else requires JDK 9+
-->
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -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<Annotation> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 1794b41

Please sign in to comment.