javaxmljaxbunmarshallingmoxy

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


Was unable to find the solution even after trying many things so posting here hoping to get some workaround or fix for this issue.

Basically, if the @XmlPath(".") has been used on a Map and if there is XMLAdapter on it then it fails during the unmarshalling. The marshaling works perfectly only the unmarshalling fails.

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 String field with @XmlElement and then I would like to perform unmarshalling.

Following is the XML that I am trying to unmarshal:

<Customer xmlns:google="https://google.com">
  <name>Batman</name>
  <age>2008</age>
  <google:sub1>MyValue-1</google:sub1>
  <google:sub2>MyValue-1</google:sub2>
</Customer>

Following is the Customer.class to which it will be unmarshalled:

@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<>();
}

Following is the Main class which will unmarshal:

public class Unmarshalling {
  public static void main(String[] args)
      throws JAXBException, XMLStreamException, FactoryConfigurationError, IOException, URISyntaxException, ProcessingException {
    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);
  }
}

When I run this code I will get the output as:

Read XML : Person(name=Rise Against, age=2000)

Its not reading the custom elements such as google:sub1 and google:sub2.

  1. There is one more issue similar to this but the workaround mentioned there did not work for me.
  2. I tried the @XmlAnyElement(lax=true) on MAP even that did not work for me.

Following is the TestAdapter that I am using for marshalling and unmarhsalling the Map<String,Object>

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;
  }

I tried many things but nothing worked till now. Can someone please have a look at this issue and provide your solution. Happy to provide any more info with regards to this issue as I have spent nearly 10 days looking for answers.

I tried following things:

  1. Created one more variable and tried to use it only during unmarshalling but that's also failing.
  2. Tried to create one more field then used the @XmlAnyElement on it but that's also not working as I expected.

Tried few more things but nothing helped.

Everything is failing. I tried to debug the code from MOXY but I am unable to follow many things there. Any help with this issue would be really helpful for me.


Solution

  • I was able to get it by using the BeforeMarshal and AfterMarshal methods. Posting here so it can be helpful to someone in the future:

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
    @JsonInclude(Include.NON_NULL)
    @JsonIgnoreProperties(ignoreUnknown = true)
    @XmlRootElement(name = "extension")
    @XmlType(name = "extension", propOrder = {"name", "age", "otherElements"})
    @XmlAccessorType(XmlAccessType.FIELD)
    @Getter
    @Setter
    @AllArgsConstructor
    @ToString
    @NoArgsConstructor
    public class Customer {
        @XmlTransient
        private String isA;
    
        @XmlPath("customer/name/text()")
        private String name;
    
        @XmlPath("customer/age/text()")
        private String age;
    
        @XmlAnyElement(lax = true)
        @JsonIgnore
        @XmlPath("customer")
        private List<Object> otherElements = new ArrayList<>();
    
        @JsonIgnore
        @XmlTransient
        private Map<String, Object> userExtensions = new HashMap<>();
    
        @JsonAnyGetter
        @JsonSerialize(using = CustomExtensionsSerializer.class)
        public Map<String, Object> getUserExtensions() {
            return userExtensions;
        }
    
        @JsonAnySetter
        public void setUserExtensions(String key, Object value) {
            userExtensions.put(key, value);
        }
    
        private void beforeMarshal(Marshaller m) throws ParserConfigurationException {
            System.out.println("Before Marshalling User Extension: " + userExtensions);
            ExtensionsModifier extensionsModifier = new ExtensionsModifier();
            otherElements = extensionsModifier.Marshalling(userExtensions);
            System.out.println("Before Marshalling Final Other Elements " + otherElements);
            userExtensions = new HashMap<>();
        }
    
        private void afterUnmarshal(Unmarshaller m, Object parent) throws ParserConfigurationException {
            System.out.println("After Unmarshalling : " + otherElements);
            ExtensionsModifier extensionsModifier = new ExtensionsModifier();
            userExtensions = extensionsModifier.Unmarshalling(otherElements);
            System.out.println("Final User Extensions : " + userExtensions);
            otherElements = new ArrayList();
        }
    }
    

    The complete answer can be found here: https://stackoverflow.com/a/67923216/7584240