Issue
Summary
Jackson annotations applied to POJOs exported through an OSGi depdendency does not work when the POJO is serialized in an importing bundle at Runtime. If the POJO is placed in the using bundle directly, or tested in a Unit Test (in either bundle), everything works as expected.
Does anyone know what could make the runtime serialization ignore the Jackson-annotations at runtime in the importing OSGi-bundle?
This is a looong question. I have tried to create an as simple example as possible. If anything is unclear, please let me know, and I will try to elaborate.
Contents
Inside Exporting Bundle
- POJO
- Unit test (which works)
- Runtime test (which works)
Inside Importing Bundle
- Unit test (which works)
- Runtime test (which FAILS)
Runtime environment details
- Bundle manifests (simplified)
Simplified example:
Let's assume we have want to serialize a simple POJO exported and imported over OSGi. The JSON-annotations should work both in the importing and exporting bundle, both runtime and during unit tests (runtime fails in the when imported).
Inside Exporting Bundle:
Both runtime and unit tests of the Jackson serialization works just fine in the bundle where the POJO itself is declared.
POJO to serialize
The @JsonProperty
-annotation should make any serialized version of this POJO look something like {"correctSerializedName":"someName"}
and not {"javaName":"someName"}
:
package exporting.osgi.bundle.models;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DependencyModel {
private String javaName;
@JsonProperty("correctSerializedName")
public String getJavaName() {
return javaName;
}
public DependencyModel(String javaName) {
this.javaName = javaName;
}
}
Correct behaviour: Unit test
import com.fasterxml.jackson.*;
import org.junit.jupiter.*;
class ExportingBundleTests {
@Test
void serialize_inDepdendencyProject_getsCorrectJsonName() {
DependencyModel dependencyModel = new DependencyModel("name");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(dependencyModel);
// Asserts True -> serialization works as expected
assertEquals(jsonString, "{\"correctSerializedName\":\"name\"}");
}
}
Correct behaviour: Runtime
public void runtimeFromDepdendency() {
DependencyModel dependencyModel = new DependencyModel("name");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(dependencyModel);
// jsonString = {"correctSerializedName":"name"}
}
Inside the importing OSGi bundle
Correct behaviour: Unit test
import exporting.osgi.bundle.models.DependencyModel;
import com.fasterxml.jackson.*;
import org.junit.jupiter.api.*;
class ImportingBundleTests {
@Test
void serialize_inUsingProject_getsCorrectJsonName() {
DependencyModel dependencyModel = new DependencyModel("name");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(dependencyModel);
// Asserts True -> serialization works as expected
assertEquals(jsonString, "{\"correctSerializedName\":\"name\"}");
}
}
FAILS: Runtime
import exporting.osgi.bundle.models.DependencyModel;
import com.fasterxml.jackson.*;
public Response runsFromUsingProject() throws JsonProcessingException {
DependencyModel dependencyModel = new DependencyModel("name");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(dependencyModel);
// jsonString = {"javaName":"name"} <---- WHICH IS WRONG
}
Runtime environment
- Jira 7.2.2 OSGi (based on Apache Felix)
- Maven 3.2.1
- Java 1.8
- Jackson 2.9.3
Exporting plugin Bundle Manifest (simplified)
Created-By: Apache Maven Bundle Plugin
Manifest-Version: 1.0
Build-Jdk: 1.8.0_111
Bundle-ManifestVersion: 2
Export-Package:
...
exporting.osgi.bundle;version="1.0.0";
uses:="exporting.osgi.bundle.models",
...
Originally-Created-By: Apache Maven Bundle Plugin
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Spring-Context: *
Tool: Bnd-2.4.1.201501161923
Importing plugin Bundle Manifest (simplified)
Created-By: Apache Maven Bundle Plugin
Manifest-Version: 1.0
Build-Jdk: 1.8.0_111
Bundle-ManifestVersion: 2
Archiver-Version: Plexus Archiver
Bundle-ClassPath: .,META-INF/lib/gt-epsg-hsql-18.1.jar,META-INF/lib/sq
lite-jdbc-3.8.11.1.jar
Import-Package:
...
exporting.osgi.bundle.models
...
com.fasterxml.jackson.dataformat.xml;resolution:=optional,
com.fasterxml.jackson.dataformat.xml.deser;resolution:=optional,
com.fasterxml.jackson.dataformat.xml.ser;resolution:=optional,
org.codehaus.jackson;resolution:=optional,
org.codehaus.jackson.annotate;resolution:=optional,
org.codehaus.jackson.map;resolution:=optional,
org.codehaus.jackson.map.annotate;resolution:=optional,
...
Originally-Created-By: Apache Maven Bundle Plugin
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Spring-Context: *
Tool: Bnd-2.4.1.201501161923
As illustrated above, the @JsonProperty
-annotation is ignored at runtime in the importing depdendency, but everything else works as expected. Again, I don't think this is a Jackson-version issue, although I might have overlooked something.
Have I perhaps missed some fundamental OSGi-behaviour?
Solution
Based on my comments and your feedback:
The problem is that your two bundles have different instances of the Jackson annotation classes. When the Jackson framework scans classes for annotations, it looks for the specific annotation class instance that it uses. If a bundle has its own instance of the same class, the annotation will not be recognised. (In OSGi, each bundle has a separate class loader, and each class loader can contain its own instance of a given class.)
You can resolve this in two ways:
- Deploy jackson2 as separate bundles to your OSGi container, and ensure that both your importing and exporting bundle have
Import-Package
on the relevant jackson packages. The most central bundles are:jackson-annotations
,jackson-core
andjackson-databind
. In this case, use scopeprovided
for the jackson2 dependencies in both your bundles. - Export the jackson2 packages from one of your bundles, and import it in the other. This will give you the extra work of figuring out and maintaining which packages to export. In this case, use scope
compile
for jackson2 in the exporting bundle, and scopeprovided
in the importing bundle.
Answered By - gjoranv
Answer Checked By - David Goodson (JavaFixing Volunteer)