Skip to content

Commit 1794b41

Browse files
authored
Merge pull request #209 from josephlbarnett/guice7
Add guice7 (jakarta.inject) module
2 parents c10dc50 + 62a9aa2 commit 1794b41

File tree

15 files changed

+983
-2
lines changed

15 files changed

+983
-2
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ Currently included are:
1010

1111
* [Afterburner](afterburner/) --
1212
* [Blackbird](blackbird/) (NEW in 2.12 -- to eventually replace Afterburner)
13-
* [Guice](guice/)
13+
* Guice
14+
* "Old" (`javax.inject`) based versions: [Guice](guice/)
15+
* New "Jakarta" (`jakarta.inject`) based versions: [Guice 7](guice7/) (added in 2.16)
1416
* Java XML Binding Annotation compatibility
1517
* "Old" (`java.xml.bind`) annotations: [JAXB Annotations](jaxb/)
1618
* New "Jakarta" (`jakarta.xml.bind`): [Jakarta XML Bind Annotations](jakarta-xmlbind/) (added in 2.13)

guice/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<version>2.16.0-SNAPSHOT</version>
1313
</parent>
1414
<artifactId>jackson-module-guice</artifactId>
15-
<name>Jackson module: Guice</name>
15+
<name>Jackson module: Guice (javax.inject)</name>
1616
<packaging>bundle</packaging>
1717

1818
<description>Stuff to make integration with Guice a bit easier</description>

guice7/README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# jackson-module-guice7
2+
3+
## Documentation
4+
5+
This is a copy of jackson-module-guice that works with guice version 7, using the jakarta.inject namespace.
6+
7+
This extension allows Jackson to delegate ObjectMapper creation and value injection to Guice when handling data bindings.
8+
Using the ObjectMapperModule you can register Jackson data binding modules like so:
9+
10+
~~~~~
11+
12+
Injector injector = Guice.createInjector(
13+
new ObjectMapperModule().registerModule(new IntegerAsBase16Module())
14+
);
15+
16+
17+
public class IntegerAsBase16Module extends SimpleModule
18+
{
19+
public IntegerAsBase16Module() {
20+
super("IntegerAsBase16");
21+
22+
addSerializer( Integer.class,
23+
new JsonSerializer<Integer>() {
24+
@Override
25+
public void serialize( Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider )
26+
throws IOException, JsonProcessingException
27+
{
28+
jsonGenerator.writeString(new BigInteger(String.valueOf(integer)).toString(16).toUpperCase());
29+
}
30+
}
31+
);
32+
}
33+
}
34+
35+
~~~~~
36+
37+
Subsequently, the ObjectMapper, created from the Guice injector above, will apply the proper data bindings to serialize
38+
Integers as base 16 strings:
39+
40+
~~~~~
41+
42+
mapper.writeValueAsString(new Integer(10)) ==> "A"
43+
44+
~~~~~
45+
46+
Additional Guice Modules can be used when creating the Injector to automatically inject values into value objects
47+
being de-serialized. The @JacksonInject annotation can be used to trigger Guice driven injection.
48+
49+
Here's an example of a value object where Guice injects three of the members on behalf of Jackson. The first
50+
uses the @JacksonInject annotation, the second uses @JacksonInject with a specific Named binding, and the
51+
third uses @JacksonInject combined with another annotation (@Ann).
52+
53+
~~~~~
54+
55+
public class SomeBean {
56+
@JacksonInject
57+
private int one;
58+
59+
@JacksonInject
60+
@Named("two")
61+
private int two;
62+
63+
@JacksonInject
64+
@Ann
65+
private int three;
66+
67+
@JsonProperty
68+
private int four;
69+
70+
public boolean verify() {
71+
Assert.assertEquals(1, one);
72+
Assert.assertEquals(2, two);
73+
Assert.assertEquals(3, three);
74+
Assert.assertEquals(4, four);
75+
return true;
76+
}
77+
}
78+
79+
~~~~~
80+
81+
The last, the fourth field, annotated with @JsonProperty uses standard ObjectMapper behavior unlike the other three
82+
which are injected by Guice. The following code snippet demonstrates Guice injection leading to a true return on the
83+
verify() method:
84+
85+
86+
~~~~~
87+
88+
final Injector injector = Guice.createInjector(
89+
new ObjectMapperModule(),
90+
new Module()
91+
{
92+
@Override
93+
public void configure(Binder binder)
94+
{
95+
binder.bind(Integer.class).toInstance(1);
96+
binder.bind(Integer.class).annotatedWith(Names.named("two")).toInstance(2);
97+
binder.bind(Integer.class).annotatedWith(Ann.class).toInstance(3);
98+
}
99+
}
100+
);
101+
102+
final ObjectMapper mapper = injector.getInstance(ObjectMapper.class);
103+
mapper.readValue("{\"four\": 4}", SomeBean.class).verify();
104+
105+
~~~~~
106+

guice7/pom.xml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<!-- This module was also published with a richer model, Gradle metadata, -->
4+
<!-- which should be used instead. Do not delete the following line which -->
5+
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
6+
<!-- that they should prefer consuming it instead. -->
7+
<!-- do_not_remove: published-with-gradle-metadata -->
8+
<modelVersion>4.0.0</modelVersion>
9+
<parent>
10+
<groupId>com.fasterxml.jackson.module</groupId>
11+
<artifactId>jackson-modules-base</artifactId>
12+
<version>2.16.0-SNAPSHOT</version>
13+
</parent>
14+
<artifactId>jackson-module-guice7</artifactId>
15+
<name>Jackson module: Guice 7+ (jakarta.inject)</name>
16+
<packaging>bundle</packaging>
17+
18+
<description>Stuff to make integration with Guice 7+ a bit easier</description>
19+
<url>https://github.com/FasterXML/jackson-modules-base</url>
20+
21+
<licenses>
22+
<license>
23+
<name>The Apache Software License, Version 2.0</name>
24+
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
25+
<distribution>repo</distribution>
26+
</license>
27+
</licenses>
28+
29+
<properties>
30+
<version.guice>7.0.0</version.guice>
31+
32+
<!-- Generate PackageVersion.java into this directory. -->
33+
<packageVersion.dir>com/fasterxml/jackson/module/guice7</packageVersion.dir>
34+
<packageVersion.package>${project.groupId}.guice7</packageVersion.package>
35+
<!-- default OSGi imports, exports should work fine -->
36+
</properties>
37+
38+
<dependencies>
39+
<!-- Extends Jackson mapper, but also uses types from core, hence direct dep as well -->
40+
<dependency>
41+
<groupId>com.fasterxml.jackson.core</groupId>
42+
<artifactId>jackson-annotations</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>com.fasterxml.jackson.core</groupId>
46+
<artifactId>jackson-core</artifactId>
47+
</dependency>
48+
<dependency>
49+
<groupId>com.fasterxml.jackson.core</groupId>
50+
<artifactId>jackson-databind</artifactId>
51+
</dependency>
52+
<dependency>
53+
<groupId>com.google.inject</groupId>
54+
<artifactId>guice</artifactId>
55+
<version>${version.guice}</version>
56+
</dependency>
57+
</dependencies>
58+
59+
<build>
60+
<plugins>
61+
<plugin>
62+
<groupId>com.google.code.maven-replacer-plugin</groupId>
63+
<artifactId>replacer</artifactId>
64+
</plugin>
65+
<!-- 14-Mar-2019, tatu: Add rudimentary JDK9+ module info. To build with JDK 8
66+
will have to use `moduleInfoFile` as anything else requires JDK 9+
67+
-->
68+
<plugin>
69+
<groupId>org.moditect</groupId>
70+
<artifactId>moditect-maven-plugin</artifactId>
71+
</plugin>
72+
</plugins>
73+
</build>
74+
75+
</project>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.fasterxml.jackson.module.guice;
2+
3+
import com.fasterxml.jackson.annotation.JacksonInject;
4+
import com.fasterxml.jackson.databind.introspect.*;
5+
import com.google.inject.BindingAnnotation;
6+
import com.google.inject.Key;
7+
8+
import jakarta.inject.Qualifier;
9+
import java.lang.annotation.Annotation;
10+
import java.util.Arrays;
11+
12+
public class GuiceAnnotationIntrospector extends NopAnnotationIntrospector
13+
{
14+
private static final long serialVersionUID = 1L;
15+
16+
@Override // since 2.9
17+
public JacksonInject.Value findInjectableValue(AnnotatedMember m) {
18+
Object id = _findGuiceInjectId(m);
19+
if (id == null) {
20+
return null;
21+
}
22+
return JacksonInject.Value.forId(id);
23+
}
24+
25+
@Deprecated // since 2.9
26+
@Override
27+
public Object findInjectableValueId(AnnotatedMember m) {
28+
return _findGuiceInjectId(m);
29+
}
30+
31+
private Object _findGuiceInjectId(AnnotatedMember m)
32+
{
33+
/*
34+
* We check on three kinds of annotations: @JacksonInject for types
35+
* that were actually created for Jackson, and @Inject (both Guice's
36+
* and jakarta.inject) for types that (for example) extend already
37+
* annotated objects.
38+
*
39+
* Postel's law: http://en.wikipedia.org/wiki/Robustness_principle
40+
*/
41+
// 19-Apr-2017, tatu: Actually this is something that should not be done;
42+
// instead, pair of AnnotationIntrospector should be used... Leaving in
43+
// for now, however.
44+
if ((m.getAnnotation(JacksonInject.class) == null) &&
45+
(m.getAnnotation(jakarta.inject.Inject.class) == null) &&
46+
(m.getAnnotation(com.google.inject.Inject.class) == null))
47+
{
48+
return null;
49+
}
50+
51+
final AnnotatedMember guiceMember;
52+
final Annotation guiceAnnotation;
53+
54+
if ((m instanceof AnnotatedField) || (m instanceof AnnotatedParameter)) {
55+
/* On fields and parameters the @Qualifier annotation and type to
56+
* inject are the member itself, so, nothing to do here...
57+
*/
58+
guiceMember = m;
59+
AnnotationMap anns = ((AnnotatedMember) m).getAllAnnotations();
60+
guiceAnnotation = findBindingAnnotation(anns.annotations());
61+
} else if (m instanceof AnnotatedMethod) {
62+
/* For method injection, the @Qualifier and type to inject are
63+
* specified on the parameter. Here, we only consider methods with
64+
* a single parameter.
65+
*/
66+
final AnnotatedMethod a = (AnnotatedMethod) m;
67+
if (a.getParameterCount() != 1) {
68+
return null;
69+
}
70+
71+
/* Jackson does not *YET* give us parameter annotations on methods,
72+
* only on constructors, henceforth we have to do a bit of work
73+
* ourselves!
74+
*/
75+
guiceMember = a.getParameter(0);
76+
final Annotation[] annotations = a.getMember().getParameterAnnotations()[0];
77+
guiceAnnotation = findBindingAnnotation(Arrays.asList(annotations));
78+
} else {
79+
/* Ignore constructors */
80+
return null;
81+
}
82+
83+
/* Depending on whether we have an annotation (or not) return the
84+
* correct Guice key that Jackson will use to query the Injector.
85+
*/
86+
if (guiceAnnotation == null) {
87+
// 19-Sep-2016, tatu: Used to pass `getGenericType()`, but that is now deprecated.
88+
// Looking at code in Guice Key, I don't think it does particularly good job
89+
// in resolving generic types, so this is probably safe...
90+
// return Key.get(guiceMember.getGenericType());
91+
return Key.get((java.lang.reflect.Type) guiceMember.getRawType());
92+
}
93+
// return Key.get(guiceMember.getGenericType(), guiceAnnotation);
94+
return Key.get((java.lang.reflect.Type) guiceMember.getRawType(), guiceAnnotation);
95+
}
96+
97+
/*
98+
* We want to figure out if a @BindingAnnotation or @Qualifier
99+
* annotation are present on what we're trying to inject.
100+
* Those annotations are only possible on fields or parameters.
101+
*/
102+
private Annotation findBindingAnnotation(Iterable<Annotation> annotations)
103+
{
104+
for (Annotation annotation : annotations) {
105+
// Check on guice (BindingAnnotation) & jakarta (Qualifier) based injections
106+
if (annotation.annotationType().isAnnotationPresent(BindingAnnotation.class) ||
107+
annotation.annotationType().isAnnotationPresent(Qualifier.class))
108+
{
109+
return annotation;
110+
}
111+
}
112+
return null;
113+
}
114+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.fasterxml.jackson.module.guice;
2+
3+
import com.fasterxml.jackson.databind.BeanProperty;
4+
import com.fasterxml.jackson.databind.DeserializationContext;
5+
import com.fasterxml.jackson.databind.InjectableValues;
6+
import com.google.inject.Injector;
7+
import com.google.inject.Key;
8+
9+
public class GuiceInjectableValues extends InjectableValues
10+
{
11+
private final Injector injector;
12+
13+
public GuiceInjectableValues(Injector injector) {this.injector = injector;}
14+
15+
@Override
16+
public Object findInjectableValue(
17+
Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance
18+
)
19+
{
20+
return injector.getInstance((Key<?>) valueId);
21+
}
22+
}

0 commit comments

Comments
 (0)