Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jackson fails to resolve XmlIDREFs on polymorphic types #170

Closed
rpatrick00 opened this issue Nov 17, 2015 · 10 comments
Closed

Jackson fails to resolve XmlIDREFs on polymorphic types #170

rpatrick00 opened this issue Nov 17, 2015 · 10 comments

Comments

@rpatrick00
Copy link

I have some Java code using JAXB annotations and a few Jackson annotations trying to work with XML code that looks like the following

<?xml version='1.1' encoding='UTF-8'?><company><computers>
    <computers>
      <desktop id="computer-1">
        <location>Bangkok</location>
      </desktop>
    </computers>
    <computers>
      <desktop id="computer-2">
        <location>Pattaya</location>
      </desktop>
    </computers>
    <computers>
      <laptop id="computer-3">
        <vendor>Apple</vendor>
      </laptop>
    </computers>
  </computers>
  <employees>
    <employee id="emp-1" name="Robert Patrick">
      <computer>computer-3</computer>
    </employee>
    <employee id="emp-2" name="Michael Smith">
      <computer>computer-2</computer>
    </employee>
  </employees>
</company>

The same code that created the file using Jackson 2.6.3 is failing to read it back in with the following exception:

java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293)
        at java.lang.Thread.run(Thread.java:745)
Caused by: com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: Unre
solved forward references for: Object id [computer-3] (for test.Computer) at [So
urce: company-jackson.xml; line: 20, column: 38], Object id [computer-2] (for te
st.Computer) at [Source: company-jackson.xml; line: 23, column: 38].
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.ch
eckUnresolvedObjectId(DefaultDeserializationContext.java:154)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMa
pper.java:3738)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.ja
va:2620)
        at test.RunThis.readJacksonFile(RunThis.java:98)
        at test.RunThis.main(RunThis.java:143)
        ... 6 more

If I remove the polymorphism, the problem goes away. The reproducer is located at https://github.com/rpatrick00/jackson-forward-reference-test. Note that I am using a custom serializer/deserializer for the polymorphic list to get around https://github.com/FasterXML/jackson-databind/issues/1004.

@cowtowncoder
Copy link
Member

If you could embed object definition for the type, it'd be simpler to look (I realize that it's within linked-to project). I suspect this is due to JAXB annotation introspector indicating that all XmlIDRefs are to be used in "always-as-id" mode, and never serialized as full POJO; if so, there isn't actual object to read back.

@rpatrick00
Copy link
Author

Here is the Java code for the types.

Computer:

@XmlSeeAlso({ LaptopComputer.class, DesktopComputer.class })
@XmlAccessorType(XmlAccessType.FIELD)
public class Computer {
  @XmlID
  @XmlAttribute
  private String id;
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
}

DesktopComputer:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "desktop")
public class DesktopComputer extends Computer {
  @XmlElement(name = "location")
  private String location;
  public String getLocation() {
    return location;
  }
  public void setLocation(String location) {
    this.location = location;
  }
}

LaptopComputer:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "laptop")
public class LaptopComputer extends Computer {
  @XmlElement(name = "vendor")
  private String vendor;
  public String getVendor() {
    return vendor;
  }
  public void setVendor(String vendor) {
    this.vendor = vendor;
  }
}

Employee:

@XmlType(name = "employee")
@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
  @XmlAttribute
  @XmlID
  private String id;
  @XmlAttribute
  private String name;
  @XmlIDREF
  private Computer computer;
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public Computer getComputer() {
    return computer;
  }
  public void setComputer(Computer computer) {
    this.computer = computer;
  }
}

Company:

@XmlRootElement(name = "company")
@XmlSeeAlso({DesktopComputer.class, LaptopComputer.class})
@XmlAccessorType(XmlAccessType.FIELD)
public class Company {
  @XmlElementWrapper(name = "employees")
  @XmlElement(name = "employee")
  private List<Employee> employees;
  @XmlElementWrapper(name = "computers")
  @XmlElements({
      @XmlElement(type = DesktopComputer.class, name = "desktop"),
      @XmlElement(type = LaptopComputer.class, name = "laptop")
  })
  @JsonDeserialize(using = ComputerListDeserializer.class)
  @JsonSerialize(using = ComputerListSerializer.class)
  private List<Computer> computers;
  public Company() {
    employees = new ArrayList();
    computers = new ArrayList<Computer>();
  }
  public List<Employee> getEmployees() {
    return employees;
  }
  public void addEmployee(Employee employee) {
    this.employees.add(employee);
  }
  public void setEmployees(List<Employee> employees) {
    this.employees = employees;
  }
  public List<Computer> getComputers() {
    return computers;
  }
  public void setComputers(List<Computer> computers) {
    this.computers = computers;
  }
  public Company addComputer(Computer computer) {
    if ( computers == null ) {
      computers = new ArrayList<Computer>();
    }
    computers.add(computer);
    return this;
  }
}

@rpatrick00
Copy link
Author

In stepping through the code in a debugger, it looks like the unresolved object IDs are being resolved except when they point to a polymorphic type so I don;t think this is a problem with there being no object--I think it is a problem with the logic used to resolve the object IDs when the element containing the reference is not the same as the class that has the ID.

In my case, the <computer>computer-3<computer> refers to <laptop id="computer-3">

@cowtowncoder
Copy link
Member

@rpatrick00 Ok. There is indeed scoping for object ids, but I am not sure if that matters here.

The method where things probably start going wrong is findObjectIdInfo() of JaxbAnnotationIntrospector. If scopes of objects do not match (reference vs id declaration) that would cause problems.
But perhaps more likely cause is that of code trying to find id property definition. Ideally this should not occur from within annotation introspector, but it is done here because I couldn't find a better way (I assume, can't remember exact details). It may be the case that base vs sub-class distinction matters, maybe id property is not find preventing linkage.

@cowtowncoder
Copy link
Member

I can reproduce the issue, looking into it now.

cowtowncoder added a commit that referenced this issue Jan 3, 2016
@cowtowncoder
Copy link
Member

Actually, I take that back. I am having issues related to wrapping elements, which seems odd.
Or perhaps it is part of the problem.

@cowtowncoder
Copy link
Member

Even weirder... once I comment out "extra" computers element (produced as per #178), test actually passes, Object Ids being resolved as expected. This with 2.7.0-rc2.

@cowtowncoder
Copy link
Member

Ok. After adding wrapper element settings etc, this is not passing. I think this particular issue is fixed for 2.7.0, unlike #178 (regarding extra wrapper element for polymorphic types).

@rpatrick00
Copy link
Author

Can you show me the changes you made to the example to get it to pass in 2.7? It does not work for me.

@cowtowncoder
Copy link
Member

@rpatrick00 I have built against local master, so it is possible fixes to databind are relevant here. I checked in the test itself as:

src/test/java/com/fasterxml/jackson/dataformat/xml/failing/JAXBObjectId170Test.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants