Jaxb ignore the namespace on unmarshalling
Asked Answered
T

3

12

I use Jaxb2 and Spring. I am trying to unmarshal some XML that are sent by 2 of my customers.

Up to now, I only had to handle one customer which sent some xml like this :

<foo xmlns="com.acme">
  <bar>[...]</bar>
<foo>

that is bound to a POJO like this :

@XmlType(name = "", propOrder = {"bar"})
@XmlRootElement(name = "Foo")
public class Foo {

  @XmlElement(name = "Bar")
  private String bar;

  [...]
}

I discovered that the previous developer hardcoded the namespace in the unmarshaller in order to make it work.

Now, the second customer sends the same XML but changes the namespace!

<foo xmlns="com.xyz">
  <bar>[...]</bar>
<foo>

Obviously, the unmarshaller fails to unmarshall because it expects some {com.acme}foo instead of {com.xyz}foo. Unforunately, asking the customer to change the XML is not an option.

What I tried :

1) In application-context.xml, I searched for a configuration which would allow me to ignore the namespace but could not find one :

<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
  <property name="packagesToScan">
    <list>
      <value>com.mycompany.mypkg</value>
    </list>
  </property>
  <property name="marshallerProperties">
    <map>
      <entry key="???"><value type="java.lang.Boolean">false</value></entry>
    </map>
  </property>
</bean>

it seems that the only available options are the ones listed in the Jaxb2Marshaller's Javadoc :

/**
 * Set the JAXB {@code Marshaller} properties. These properties will be set on the
 * underlying JAXB {@code Marshaller}, and allow for features such as indentation.
 * @param properties the properties
 * @see javax.xml.bind.Marshaller#setProperty(String, Object)
 * @see javax.xml.bind.Marshaller#JAXB_ENCODING
 * @see javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT
 * @see javax.xml.bind.Marshaller#JAXB_NO_NAMESPACE_SCHEMA_LOCATION
 * @see javax.xml.bind.Marshaller#JAXB_SCHEMA_LOCATION
 */
public void setMarshallerProperties(Map<String, ?> properties) {
    this.marshallerProperties = properties;
}

2) I also tried to configure the unmarshaller in the code :

try {
  jc = JAXBContext.newInstance("com.mycompany.mypkg");

  Unmarshaller u = jc.createUnmarshaller();
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  dbf.setNamespaceAware(false);//Tried this option.

  DocumentBuilder db = dbf.newDocumentBuilder();
  Document doc = db.parse(xmlFile.toFile());
  u.unmarshal(new DOMSource(doc));
  return (Foo)u.unmarshal(new StreamSource(xmlFile.toFile()));
} catch (ParserConfigurationException | SAXException | IOException | JAXBException e) {
  LOGGER.error("Erreur Unmarshalling CPL");
}

3) Different form with a SAXParser :

try {
  jc = JAXBContext.newInstance("com.mycompany.mypkg");
  Unmarshaller um = jc.createUnmarshaller();
  final SAXParserFactory sax = SAXParserFactory.newInstance();
  sax.setNamespaceAware(false);
  final XMLReader reader = sax.newSAXParser().getXMLReader();
  final Source er = new SAXSource(reader, new InputSource(new FileReader(xmlFile.toFile())));
  return (Foo)um.unmarshal(er);
}catch(...) {[...]}

This one works! But still, I would prefer to be able to autowire the Unmarshaller without needing this ugly conf everytime.

Truck answered 14/4, 2015 at 8:44 Comment(0)
E
2

Namesapce awareness is feature of the document reader/builder/parser not marshallers. XML elements from different namespaces represents different entities == objects, so marshallers cannot ignore them.

You correctly switched off the namespaces in your SAX reader and as you said it worked. I don't understand your problem with it, your marshaller still can be injected, the difference is in obtaining the input data.

The same trick with document builder should also work (I will test it later on), I suspect that you were still using the marshaller with "hardcoded" namespace but your document was namespace free.

In my project I use XSLT to solve similar issue. Setting namespace awarness is definitely easier solution. But, with XSLT I could selectviely remove only some namespaces and also my my input xml are not always identical (ignoring namespaces) and sometimes I have to rename few elements so XSLT gives me this extra flexibility.

To remove namespaces you can use such xslt template:

<xsl:stylesheet version="1.0" xmlns:e="http://timet.dom.robust.ed" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:template match="/"> 
    <xsl:copy>
        <xsl:apply-templates />
    </xsl:copy>
</xsl:template>  

<xsl:template match="*">
    <xsl:element name="{local-name()}">
        <xsl:apply-templates select="@* | node()" />
    </xsl:element>
</xsl:template>

<xsl:template match="@*">
    <xsl:attribute name="{local-name()}">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>

<xsl:template match="text() | processing-instruction() | comment()">
    <xsl:copy />
</xsl:template>
</xsl:stylesheet>

Then in Java before unmarshalling I transform the input data:

Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
Source source = new DOMSource(xml);
DOMResult result = new DOMResult();
transformer.transform(source, result);
Eleventh answered 14/4, 2015 at 12:27 Comment(1)
I tried a similar approach but did not work for me so I have added the question here can you please once check and respond? https://mcmap.net/q/244471/-how-to-unmarshall-a-xml-without-namespace-using-the-jakarta-xml-unmarshaller/7584240Shep
T
2

I've followed Java docs to handle namespace in xml while unmarshalling, it did the trick.

   JAXBContext jc = JAXBContext.newInstance( "com.acme.foo" );
   Unmarshaller u = jc.createUnmarshaller();

   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
   dbf.setNamespaceAware(false);
   DocumentBuilder db = dbf.newDocumentBuilder();
   Document doc = db.parse(new File( "nosferatu.xml"));
   //If just a string
   //InputSource is = new InputSource(new StringReader(line));
   //Document doc = db.parse(is)
   Object o = u.unmarshal( doc );

Used Java API JAXB Context and Unmarshal features. https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/Unmarshaller.html

Ticker answered 20/4, 2022 at 6:29 Comment(0)
R
1

Thank you all, here shared my solution which works for my code , i try to make it generic every namespace contain ": " i write code if any tag have ":" it will remove from xml , This is used to skip namespace during unmarshalling using jaxb.

public class NamespaceFilter {

private NamespaceFilter() {

}

private static final String COLON = ":";

public static XMLReader nameSpaceFilter() throws SAXException {
    XMLReader xr = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
        private boolean skipNamespace;

        @Override
        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
            if (qName.indexOf(COLON) > -1) {
                skipNamespace = true;
            } else {
                skipNamespace = false;
                super.startElement("", localName, qName, atts);
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.indexOf(COLON) > -1) {
                skipNamespace = true;
            } else {
                skipNamespace = false;
                super.endElement("", localName, qName);
            }
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            if (!skipNamespace) {
                super.characters(ch, start, length);
            }
        }
    };
    return xr;
}

}

for unmarshalling

XMLReader xr = NamespaceFilter.nameSpaceFilter();
Source src = new SAXSource(xr, new InputSource("C:\\Users\\binal\\Desktop\\response.xml"));
StringWriter sw = new StringWriter();
Result res = new StreamResult(sw);
TransformerFactory.newInstance().newTransformer().transform(src, res);
JAXBContext jc = JAXBContext.newInstance(Tab.class);
Unmarshaller u = jc.createUnmarshaller();
String done = sw.getBuffer().toString();
StringReader reader = new StringReader(done);
Tab tab = (Tab) u.unmarshal(reader);

System.out.println(tab);

`

Rossen answered 22/4, 2019 at 7:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.