eclipselink icon indicating copy to clipboard operation
eclipselink copied to clipboard

JAXB/Moxy @XmlPath(".") conflicts with XMLAdapter during the unmarshalling.

Open Aravinda93 opened this issue 3 years ago • 2 comments

In short, I would like to perform the unmarshalling as mentioned here but along with Map I will have one more @XmlElement. So one field is annotated with (Map field) @XmlPath(".") and another field with (String field) @XmlElement and then I would like to perform unmarshalling.

I had opened this issue and was thinking somethings wrong with my code but after trying a lot of things and researching I found out that the issue is actually with the MOXY.

Basically, if the MOXY is used with @XmlPath(".") for and Map<String, Object> and if the XMLAdapter has been used for marshaling and unmarshalling then the marshaling will work fine as expected but the unmarshalling fails with the following error:

jakarta.xml.bind.UnmarshalException
 - with linked exception:
[Exception [EclipseLink-25004] (Eclipse Persistence Services - 3.0.0.v202012081010): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred unmarshalling the document
Internal Exception: java.lang.NullPointerException: Cannot invoke "org.eclipse.persistence.internal.oxm.mappings.UnmarshalKeepAsElementPolicy.isKeepUnknownAsElement()" because "keepAsElementPolicy" is null]

I was able to locate and ticket which was logged back in 2015 but its status is still NEW and when I checked the code it seems like it has been not fixed yet. I am using the latest version of the Moxy 3.0.0.

I tried to modify the code as mentioned within the ticket and then added that JAR to my code but even that did not work. I would kindly request you to please look into this issue and provide some solution. I have provided a simple example with code in the below comment

Aravinda93 avatar Jun 04 '21 17:06 Aravinda93

In short, I would like to perform the unmarshalling as mentioned here but along with Map I will have one more @XmlElement. So one field is annotated with (Map field) @XmlPath(".") and another field with (String field) @XmlElement and then I would like to perform unmarshalling.

I tried to modify the source code and then used the JAR from the updated code but still, the issue has not been fixed.

Following the XML that I am trying to read:

<Customer xmlns:google="https://google.com">
  <name>Rise Against</name>
  <age>2000</age>
  <google:sub1>MyValue-1</google:sub1>
  <google:sub2>MyValue-1</google:sub2>
</Customer>

Following is the Customer.class that I have:

@JsonInclude(Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
@XmlRootElement(name = "Customer")
@XmlType(name = "Customer", propOrder = {"name", "age", "userExtensions"})
@XmlAccessorType(XmlAccessType.FIELD)
@NoArgsConstructor
@Getter
@Setter
@AllArgsConstructor
public class Customer extends Person {

  private String name;
  private String age;

  @XmlPath(".")
  @XmlJavaTypeAdapter(TestAdapter.class)
  @XmlAnyElement
  private Map<String, Object> userExtensions = new HashMap<>();

  @JsonAnySetter
  public void setUserExtensions(String key, Object value) {
    System.out.println("Key : " + key + " Value : " + value);
    userExtensions.put(key, value);
  }

  @JsonAnyGetter
  @JsonSerialize(using = CustomExtensionsSerializer.class)
  public Map<String, Object> getUserExtensions() {
    return this.userExtensions;
  }
}

The Marshalling works as expected but during the unmarshalling it fails to read the custom-element that are provided such as google:sub1 and google:sub2.

public class Unmarshalling {

  public static void main(String[] args) throws JAXBException, XMLStreamException, FactoryConfigurationError, JsonProcessingException {
    final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("customer.xml");
    final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
    final JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class);
    final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    final Customer customer = unmarshaller.unmarshal(xmlStreamReader, Customer.class).getValue();
    System.out.println("Read XML : " + customer);
  }
}

Using the updated code and JAR (Updating the resultNode = null): The System.out just prints the Name and Age but it does not display the user extensions that have been provided.

Using the normal code and JAR (with resultNode = xPathNode.getAnyNode();) The System.out just does not print anything. All the values are NULL including name and age.

So basically there is some conflict while using the @XmlPath(".") and XMLAdapter.

I would kindly request you to look into this issue and provide some workaround or suggestions.

Following is my Adapter:

public class TestAdapter extends XmlAdapter<Wrapper, Map<String, Object>> {

  @Override
  public Map<String, Object> unmarshal(Wrapper value) throws Exception {
    System.out.println("ADAPTER UNMARSHALLING");
    System.out.println(value.getElements());

    if (value == null) {
      return null;
    }

    //Loop across all elements within ILMD tag
    final Map<String, Object> extensions = new HashMap<>();
    for (Object obj : value.getElements()) {
      Element element = (Element) obj;

      //System.out.println("Node Name : " + element.getNodeName() + " Value : " + element.getTextContent());

      final NodeList children = element.getChildNodes();

      if (children.getLength() == 1) {
        extensions.put(element.getNodeName(), element.getTextContent());
      } else {
        List<Object> child = new ArrayList<>();
        for (int i = 0; i < children.getLength(); i++) {
          final Node n = children.item(i);
          if (n.getNodeType() == Node.ELEMENT_NODE) {
            Wrapper wrapper = new Wrapper();
            List childElements = new ArrayList();
            childElements.add(n);
            wrapper.elements = childElements;
            child.add(unmarshal(wrapper));
          }
        }
        extensions.put(element.getNodeName(), child);
      }
    }
    return extensions;
  }

  @SuppressWarnings("unchecked")
  @Override
  public Wrapper marshal(Map<String, Object> v) throws Exception {
    if (v == null) {
      return null;
    }

    Wrapper wrapper = new Wrapper();
    List elements = new ArrayList();
    for (Map.Entry<String, Object> property : v.entrySet()) {
      if (property.getValue() instanceof Map) {
        elements.add(new JAXBElement<Wrapper>(new QName(property.getKey()), Wrapper.class, marshal((Map) property.getValue())));
      } else {
        elements.add(new JAXBElement<String>(new QName(property.getKey()), String.class, property.getValue().toString()));
      }
    }
    wrapper.elements = elements;
    return wrapper;
  }
}

class Wrapper {

  @XmlAnyElement
  List elements;

  public List getElements() {
    return elements;
  }
}

Aravinda93 avatar Jun 06 '21 09:06 Aravinda93

Hello, in the attached test case is possible solution/workaround for this issue. There is used following declaration

...
@XmlRootElement(name = "Customer")
@XmlType(name = "Customer", propOrder = {"name", "age", "userExtensions"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
    private String name;
    private String age;

    @XmlAnyElement(value = TestDomHandler.class)
    private Map<String, Object> userExtensions = new HashMap<>();
...

Instead of @XmlPath(".") and @XmlJavaTypeAdapter is @XmlAnyElement(value = TestDomHandler.class) used. com.oracle.moxy.bugtest.domain.TestDomHandler.getElement() contains additional XML parsing logic to parse and map other elements than <name> and <age> into private Map<String, Object> userExtensions. Please check attached Maven project is this solution suits to You.

moxy-bug-1181-JAXBMoxyXmlPathConflictsWithXMLAdapterDuringUnmarshallingSolution10.tar.gz

rfelcman avatar Oct 12 '21 12:10 rfelcman