简体   繁体   中英

JAXB: Unmarshalling similar XMLs with varying namespaces

I am getting 3 different xml input from 3 different clients:

Input 1 from Client 1: (namespace-prefix: ns2 , namespace-url: url1 )

<ns2:myroot xmlns:ns2="url1">
   <ns2:mydata>
      <ns2:myrate>
         GBP 800.55
      </ns2:myrate>
   </ns2:mydata>
</ns2:myroot>

Input 2 from Client 2: (namespace-prefix: ns5 , namespace-url: url2 )

<ns5:myroot xmlns:ns5="url2">
   <ns5:mydata>
      <ns5:myrate>
         THB 1000.70
      </ns5:myrate>
   </ns5:mydata>
</ns5:myroot>

Input 3 from Client 3: (namespace-prefix: <empty> , namespace-url: url3 )

<myroot xmlns="url3">
   <mydata>
      <myrate>
         USD 955.25
      </myrate>
   </mydata>
</myroot>

All 3 of them have same element names but different namespaces. So I wrote down 1 package of class files named MyRoot.java and MyData.java :

@XmlRootElement(name="myroot")
@XmlAccessorType(XmlAccessType.FIELD)
public class MyRoot
{   @XmlElement(name="mydata")
    protected MyData mydata;
    public MyData getMyData()
    {   return mydata;
    }
}


@XmlAccessorType(XmlAccessType.FIELD)
public class MyData
{   @XmlElement(name="myrate")
    protected String myRate;
    public String getMyRate()
    {   return myRate;
    }
}

Here is how I tested it:

public class Test {
    public static void main(String[]ar){
        for(String inputFileName:new String[]{"input3.xml","input2.xml","input1.xml"}){
            try{
                MyRoot myRoot = (MyRoot) JAXBContext.newInstance(MyRoot.class).createUnmarshaller().unmarshal(Test.class.getClassLoader().getResourceAsStream("data/xml/jaxb/varns/"+inputFileName));
                System.out.println(myRoot.getMyData());
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

My Problem is:

  1. If I add namespace="url3" to @XmlRootElement / @XmlElement , then I get: javax.xml.bind.UnmarshalException: unexpected element (uri:"url1", local:"myroot"). Expected elements are <{url3}myroot> javax.xml.bind.UnmarshalException: unexpected element (uri:"url1", local:"myroot"). Expected elements are <{url3}myroot> and javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"myroot"). Expected elements are <{url3}myroot> javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"myroot"). Expected elements are <{url3}myroot>
  2. If I add namespace="url2" to @XmlRootElement / @XmlElement , then I get: javax.xml.bind.UnmarshalException: unexpected element (uri:"url1", local:"myroot"). Expected elements are <{url2}myroot> javax.xml.bind.UnmarshalException: unexpected element (uri:"url1", local:"myroot"). Expected elements are <{url2}myroot> and javax.xml.bind.UnmarshalException: unexpected element (uri:"url3", local:"myroot"). Expected elements are <{url2}myroot> javax.xml.bind.UnmarshalException: unexpected element (uri:"url3", local:"myroot"). Expected elements are <{url2}myroot>
  3. If I add namespace="url1" to @XmlRootElement / @XmlElement , then I get: javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"myroot"). Expected elements are <{url1}myroot> javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"myroot"). Expected elements are <{url1}myroot> and javax.xml.bind.UnmarshalException: unexpected element (uri:"url3", local:"myroot"). Expected elements are <{url1}myroot> javax.xml.bind.UnmarshalException: unexpected element (uri:"url3", local:"myroot"). Expected elements are <{url1}myroot>

My Question is:

Is there any way I can make this code suitable for varying namespaces?

Since I needed the code to work for different namespaces for each input, I made following change s in the code:

  1. Made the variable NAMESPACE in NamespaceFilter non-static.
  2. Changed the Demo.java code to MyRootUtils.java that has mehtod named getMyRoot
  3. On using JaxbContext.newInstance("url1") I found that there is no such newInstance() which takes namespace-url as an argument. So I changed it to: newInstance(Myroot.class) .
  4. Changed the Test.java to suit the new classes.

MyRootUtils.java

package data.xml.jaxb.varns1;
import java.io.InputStream;
import javax.xml.bind.*;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.*;
public class MyRootUtils {
    public static MyRoot getMyRoot(String prefix,String url,InputStream xml) throws Exception {
        // Create the JAXBContext
        JAXBContext jc = JAXBContext.newInstance(MyRoot.class);//(MyRoot.class);
        // Create the XMLFilter
        XMLFilter filter = new NamespaceFilter(prefix,url);
        // Set the parent XMLReader on the XMLFilter
        filter.setParent(SAXParserFactory.newInstance().newSAXParser().getXMLReader());
        // Set UnmarshallerHandler as ContentHandler on XMLFilter
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        UnmarshallerHandler unmarshallerHandler = unmarshaller.getUnmarshallerHandler();
        filter.setContentHandler(unmarshallerHandler);
        // Parse the XML
        filter.parse(new InputSource(xml));
        return (MyRoot) unmarshallerHandler.getResult();
    }
}

NamespaceFilter.java

package data.xml.jaxb.varns1;
import org.xml.sax.*;
import org.xml.sax.helpers.XMLFilterImpl;
public class NamespaceFilter extends XMLFilterImpl {
    private final String NAMESPACE,PREFIX;
    public NamespaceFilter(String prefix,String url) {
        PREFIX=prefix;
        NAMESPACE=url;
    }
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        System.out.println("::Into endElement(uri,localName,qName):: [uri: "+uri+"],[localName: "+localName+"],[qName: "+qName+"];");
        super.endElement(NAMESPACE, localName, qName);
    }
    @Override
    public void startElement(String uri, String localName, String qName,Attributes atts)
            throws SAXException {
        System.out.println("::Into startElement(uri,localName,qName,atts):: [uri: "+uri+"],[localName: "+localName+"],[qName: "+qName+"];");
        super.startElement(NAMESPACE, localName, qName, atts);
    }
    @Override
    public void startPrefixMapping(String prefix, String uri)
            throws SAXException {
        System.out.println(prefix+"="+uri);
        super.startPrefixMapping(PREFIX, NAMESPACE);
    }
    @Override
    public void endPrefixMapping(String prefix) throws SAXException {
        super.endPrefixMapping(PREFIX);
    }        
}

Test.java

package data.xml.jaxb.varns1;
import java.io.InputStream;    
public class Test {
    public static void main(String[]ar){
        String[] files={"input3.xml","input2.xml","input1.xml"};
        String[] urls ={"url3","url2","url1"};
        String[] prfs ={"","ns5","ns2"};
        for(int i = 0 ; i < 3 ; ++i){
            try{
                String prefix=prfs[i];
                String url = urls[i];
                InputStream xml = Test.class.getClassLoader().getResourceAsStream("data/xml/jaxb/varns1/"+files[i]);
                MyRoot myRoot = MyRootUtils.getMyRoot(prefix,url, xml);
                System.out.println(myRoot.getMyData().getMyRate());
            }catch (Exception e) {
                System.err.println(e.getCause());
            }
        }
    }
}

However I still got this output:

::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: myroot];
javax.xml.bind.UnmarshalException: unexpected element (uri:"url3", local:"myroot"). Expected elements are <{}myroot>
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns5:myroot];
javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"ns5:myroot"). Expected elements are <{}myroot>
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns2:myroot];
javax.xml.bind.UnmarshalException: unexpected element (uri:"url1", local:"ns2:myroot"). Expected elements are <{}myroot>

Which meant something was missing.

Here is the full stacktrace of one of the exceptions(namespace without prefix):

javax.xml.bind.UnmarshalException: unexpected element (uri:"url3", local:"myroot"). Expected elements are <{}myroot>
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:648)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:236)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:231)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:105)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext$DefaultRootLoader.childElement(UnmarshallingContext.java:1051)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:484)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:465)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.startElement(InterningXmlVisitor.java:60)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.startElement(SAXConnector.java:135)
    at org.xml.sax.helpers.XMLFilterImpl.startElement(XMLFilterImpl.java:527)
    at data.xml.jaxb.varns1.NamespaceFilter.startElement(NamespaceFilter.java:20)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:501)
    at com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.startElement(XMLDTDValidator.java:767)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1363)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$ContentDriver.scanRootElementHook(XMLDocumentScannerImpl.java:1315)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3104)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:921)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:647)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
    at org.xml.sax.helpers.XMLFilterImpl.parse(XMLFilterImpl.java:333)
    at data.xml.jaxb.varns1.MyRootUtils.getMyRoot(MyRootUtils.java:31)
    at data.xml.jaxb.varns1.Test.main(Test.java:15)
Caused by: javax.xml.bind.UnmarshalException: unexpected element (uri:"url3", local:"myroot"). Expected elements are <{}myroot>
    ... 27 more

Here is another stacktrace (namespace with prefix):

javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"ns5:myroot"). Expected elements are <{}myroot>
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:648)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:236)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:231)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:105)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext$DefaultRootLoader.childElement(UnmarshallingContext.java:1051)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:484)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:465)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.startElement(InterningXmlVisitor.java:60)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.startElement(SAXConnector.java:135)
    at org.xml.sax.helpers.XMLFilterImpl.startElement(XMLFilterImpl.java:527)
    at data.xml.jaxb.varns1.NamespaceFilter.startElement(NamespaceFilter.java:20)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:501)
    at com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.startElement(XMLDTDValidator.java:767)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1363)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$ContentDriver.scanRootElementHook(XMLDocumentScannerImpl.java:1315)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3104)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:921)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:647)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
    at org.xml.sax.helpers.XMLFilterImpl.parse(XMLFilterImpl.java:333)
    at data.xml.jaxb.varns1.MyRootUtils.getMyRoot(MyRootUtils.java:31)
    at data.xml.jaxb.varns1.Test.main(Test.java:15)
Caused by: javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"ns5:myroot"). Expected elements are <{}myroot>
... 27 more

Adding namespace="url1" is of no use either, as I still got same stack of exceptions.

The output: (without namespace="url1" )

::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns5:myroot];
javax.xml.bind.UnmarshalException: unexpected element (uri:"url2", local:"ns5:myroot"). Expected elements are <{}myroot>

The output: (with namespace="url1" )

::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns2:myroot];
javax.xml.bind.UnmarshalException: unexpected element (uri:"url1", local:"ns2:myroot"). Expected elements are <{url1}myroot>

Shows that namespace resolution is not yet complete.

Finally, I made 2 more changes:

  1. I used the .replace("old-string","new-string") method of String class in NamespaceFilter.java to reduce qName (The fully qualified element-name), like this:

    super.endElement(NAMESPACE, localName, qName);

    changed to:

    super.endElement("", localName, qName.replace(PREFIX+":", ""));

  2. super.startElement(NAMESPACE, localName, qName, atts);

    changed to:

    super.startElement("", localName, qName.replace(PREFIX+":", ""), atts);

Code for NamespaceFilter.java :

package data.xml.jaxb.varns1;
import org.xml.sax.*;
import org.xml.sax.helpers.XMLFilterImpl;
public class NamespaceFilter extends XMLFilterImpl {
    private final String NAMESPACE,PREFIX;
    public NamespaceFilter(String prefix,String url) {
        PREFIX=prefix;
        NAMESPACE=url;
    }
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        System.out.println("::Into endElement(uri,localName,qName):: [uri: "+uri+"],[localName: "+localName+"],[qName: "+qName+"];");
        super.endElement("", localName, qName.replace(PREFIX+":", ""));
    }
    @Override
    public void startElement(String uri, String localName, String qName,Attributes atts)
            throws SAXException {
        System.out.println("::Into startElement(uri,localName,qName,atts):: [uri: "+uri+"],[localName: "+localName+"],[qName: "+qName+"];");
        super.startElement("", localName, qName.replace(PREFIX+":", ""), atts);
    }
    @Override
    public void startPrefixMapping(String prefix, String uri)
            throws SAXException {
        System.out.println(prefix+"="+uri);
        super.startPrefixMapping(PREFIX, NAMESPACE);
    }
    @Override
    public void endPrefixMapping(String prefix) throws SAXException {
        super.endPrefixMapping(PREFIX);
    }
}

The output now is:

::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: myroot];
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: mydata];
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: myrate];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: myrate];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: mydata];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: myroot];
USD 955.25
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns5:myroot];
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns5:mydata];
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns5:myrate];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: ns5:myrate];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: ns5:mydata];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: ns5:myroot];
THB 1000.70
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns2:myroot];
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns2:mydata];
::Into startElement(uri,localName,qName,atts):: [uri: ],[localName: ],[qName: ns2:myrate];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: ns2:myrate];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: ns2:mydata];
::Into endElement(uri,localName,qName):: [uri: ],[localName: ],[qName: ns2:myroot];
GBP 800.55

However, this raises a few concerns:

  1. Ignores the namespace completely.
  2. Calls String.replace() each time there is a startElement() & endElement()

It makes me think if this approach for variable-namespace should be acceptable at all. If there is anything that can be done to improve this code, then I request the community to mention it in the answers/comments and help do away with this probably flawed approach.

Handling of XML from Primary Namespace

For any XML that corresponds directly to the namespace that you have mapped to in the Java model you can marshal/unmarshal it normally

Handling of XML from Secondary Namespaces

Unmarshalling

You can apply a SAX XMLFilter when unmarshalling to that when you are processing an XML document that corresponds to the schema with namespace url2 it appears as though it corresponds to the schema with namespace url1 .

import org.xml.sax.*;
import org.xml.sax.helpers.XMLFilterImpl;

public class NamespaceFilter extends XMLFilterImpl {

    private static final String NAMESPACE = "url1";

    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        super.endElement(NAMESPACE, localName, qName);
    }

    @Override
    public void startElement(String uri, String localName, String qName,
            Attributes atts) throws SAXException {
        super.startElement(NAMESPACE, localName, qName, atts);
    }

}

Demo Code

Here is some sample code demonstrating how to apply the XMLFilter .

import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.xml.sax.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        // Create the JAXBContext
        JAXBContext jc = JAXBContext.newInstance("url1");

        // Create the XMLFilter
        XMLFilter filter = new NamespaceFilter();

        // Set the parent XMLReader on the XMLFilter
        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser sp = spf.newSAXParser();
        XMLReader xr = sp.getXMLReader();
        filter.setParent(xr);

        // Set UnmarshallerHandler as ContentHandler on XMLFilter
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        UnmarshallerHandler unmarshallerHandler = unmarshaller
                .getUnmarshallerHandler();
        filter.setContentHandler(unmarshallerHandler);

        // Parse the XML
        InputSource xml = new InputSource("input.xml");
        filter.parse(xml);
        Object result = unmarshallerHandler.getResult();
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM