Marshalling and Unmarshalling changes xml using moxy - java

I am trying to create this kind(xsd inside) of documents. some examples are here. Because of constant values in root-element and some other constant elements i generated a template with eclipse:
<?xml version="1.0" encoding="UTF-8"?>
<invoice:response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:invoice="http://www.forum-datenaustausch.ch/invoice" xmlns="http://www.forum-datenaustausch.ch/invoice" xsi:schemaLocation="http://www.forum-datenaustausch.ch/invoice generalInvoiceResponse_440.xsd" language="de">
<invoice:processing>
<invoice:transport from="" to="">
<invoice:via sequence_id="0" via=""/>
</invoice:transport>
</invoice:processing>
<invoice:payload response_timestamp="0">
<invoice:invoice request_date="2001-12-31T12:00:00" request_id="" request_timestamp="0"/>
</invoice:payload>
</invoice:response>
But simple unmarshalling and marshalling changes the content:
<?xml version="1.0" encoding="UTF-8"?>
<response xmlns="http://www.forum-datenaustausch.ch/invoice" xmlns:ns1="http://www.w3.org/2000/09/xmldsig#" xmlns:ns0="http://www.w3.org/2001/04/xmlenc#" language="de">
<processing>
<transport from="" to="">
<via via="" sequence_id="0"/>
</transport>
</processing>
<payload response_timestamp="0">
<invoice request_timestamp="0" request_date="2001-12-31T12:00:00.0" request_id=""/>
</payload>
</response>
for some reason the schema location attribute is gone. this could be added manually before marshalling. the 2nd problem is, all prefixes are gone.
i don't know who consumes the produced xml (do they unmarshal with handwritten code? with or without validation?). Because of this i want an output that is most similar to given examples and valid.
So is there either a way to leave existing elements and attributes untouched and to let moxy add namespace prefixes to each element?

The following should help. This question is also being handled on the EclipseLink forum:
http://www.eclipse.org/forums/index.php/t/487391/
for some reason the schema location attribute is gone.
You can specify the following property on the Marshaller to output a schema location:
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.forum-datenaustausch.ch/invoice generalInvoiceResponse_440.xsd");
2nd problem is, all prefixes are gone.
The namespace prefixes are gone, but the namespace qualification is the same (all elements have the same local name and namespace URI). In the first document the invoice prefix is assigned to the http://www.forum-datenaustausch.ch/invoice namespace, and in the second document that namespace is assigned as the default namespace
CONTROLLING NAMESPACE PREFIXES AT DESIGN TIME
You can provide MOXy hints at what namespace prefixes should be used by leveraging the #XmlSchema annotation (see: http://blog.bdoughan.com/2011/11/jaxb-and-namespace-prefixes.html).
package-info
#XmlSchema(
elementFormDefault=XmlNsForm.QUALIFIED,
namespace="http://www.forum-datenaustausch.ch/invoice",
xmlns={
#XmlNs(prefix="invoice", namespaceURI="http://www.forum-datenaustausch.ch/invoice"),
#XmlNs(prefix="ds", namespaceURI="http://www.w3.org/2000/09/xmldsig#"),
#XmlNs(prefix="xenc", namespaceURI="http://www.w3.org/2001/04/xmlenc#")
}
)
package forum16559889;
import javax.xml.bind.annotation.*;
Response
package forum16559889;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Response {
}
Demo
package forum16559889;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Response.class);
Response response = new Response();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.forum-datenaustausch.ch/invoice generalInvoiceResponse_440.xsd");
marshaller.marshal(response, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<invoice:response xsi:schemaLocation="http://www.forum-datenaustausch.ch/invoice generalInvoiceResponse_440.xsd" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:invoice="http://www.forum-datenaustausch.ch/invoice" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
CONTROLLING THE NAMESPACE PREFIXES AT RUNTIME
You cam leverage MOXy's NamespacePrefixMapper extension to control the namespace prefixes used at runtime.
package forum16559889;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.oxm.NamespacePrefixMapper;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Response.class);
Response response = new Response();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.forum-datenaustausch.ch/invoice generalInvoiceResponse_440.xsd");
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, new NamespacePrefixMapper() {
#Override
public String getPreferredPrefix(String namespaceUri,
String suggestion, boolean requirePrefix) {
if("http://www.forum-datenaustausch.ch/invoice".equals(namespaceUri)) {
return "invoice";
} else if("http://www.w3.org/2000/09/xmldsig#".equals(namespaceUri)) {
return "ds";
} else if("http://www.w3.org/2001/04/xmlenc#".equals(namespaceUri)) {
return "xenc";
} else {
return null;
}
}
});
marshaller.marshal(response, System.out);
}
}

Related

JAXB ~ Dynamically parse multiple namespaces

I am trying to create an unmarshaller that will work for the following XML files:
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd
xml:lang="en">
[...]
</REQ-IF>
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:configuration="http://eclipse.org/rmf/pror/toolextensions/1.0"
xmlns:id="http://pror.org/presentation/id"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
[...]
</REQ-IF>
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:doors="http://www.ibm.com/rdm/doors/REQIF/xmlns/1.0"
xmlns:reqif="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:reqif-common="http://www.prostep.org/reqif"
xmlns:reqif-xhtml="http://www.w3.org/1999/xhtml"
xmlns:rm="http://www.ibm.com/rm"
xmlns:rm-reqif="http://www.ibm.com/rm/reqif"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
[...]
</REQ-IF>
All those files are structurally the same and are based of the same top-level namespace, but also contain a variety of variable sub-level namespaces and other "things" (which by my understanding should be attributes, but are not), which need to be saved in the system.
Thus far, I have managed to get to the point where this much is saved:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd">
[...]
</REQ-IF>
however, my intended result would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd
xml:lang="en">
[...]
</REQ-IF>
So the top-level namespace is saved, but the sub-level namespaces and other "things" are lost in the import/export process. This is bad.
How can I save those other sub-namespaces and other "things", considering that they are dynamically generated?
Basically, what I want to say is "save all these extra attributes in any way you like while parsing the XML, and once you export the XML again, re-write them exactly as they were".
If your main use case is to read ReqIF files, consider that there is an open source implementation of a ReqIF (de)serializer at https://www.eclipse.org/rmf/
Unfortunately it seems that JAXB alone is not capable of manage all the namespace prefixes dynamically and you need to combine it with another parsing mechanism.
I would try to implement something like this (only rough implementation, details below):
public class MyXmlHandler {
XMLInputFactory xif = XMLInputFactory.newInstance();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLEventFactory xef = XMLEventFactory.newInstance();
/**
* Retrieve XMLEvent for root element
*/
public StartElement getStartElement(String source) throws XMLStreamException {
XMLEvent event;
XMLEventReader reader = xif.createXMLEventReader(new StringReader(source));
while (reader.hasNext()) {
event = reader.nextEvent();
if (event.isStartElement()) {
return event.asStartElement();
}
// alternativery you can retrieve here also QNames for first level child elements
// and return all this data in some synthetic wrapper class
}
return null; // alternatively throw an exception
}
/**
* Write root element, than some content from JAXB elements, than end element
*/
public void write(
Marshaller marshaller,
Writer writer,
StartElement root,
List<JAXBElement> elements
) throws JAXBException, XMLStreamException {
XMLEventWriter xew = xof.createXMLEventWriter(writer);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
xew.add(root);
for(JAXBElement element : elements) {
marshaller.marshal(element, xew);
}
xew.add(xef.createEndElement(root.getName(), root.getNamespaces()));
xew.close();
}
}
And use it like this:
// create JAXB context and unmarshaller
JAXBContext ctx = JAXBContext.newInstance(RootClass.class);
Unmarshaller unmarshaller = ctx.createUnmarshaller();
// unmarshall XML
JAXBElement<RootClass> element = unmarshaller.unmarshal(source, RootClass.class);
RootClass rootValue = element.getValue();
// extract root element data from XML
StartElement root = handler.getStartElement(data);
// perform some business logic
// create marshaller
Marshaller marshaller = ctx.createMarshaller();
// create list of JAXBElements for root children
List<JAXBElement> elements = new ArrayList<>();
QName qname = ... // construct qualified name or retrieve it from saved structure
// very schematic, names of the child elements depend on your implementation
elements.add(new JAXBElement(qname , rootValue.getChild().getClass(), rootValue.getChild()));
handler.write(marshaller, writer, root, elements);
When preserving root element data, you can save also QNames for its children in some wrapper class. So far I see the REQ-IF structure contains header, core content and tool extensions. You could save QNames for all of them and then use them for constructing JAXB elements during marshalling process.

Optional Namespace parsing XML via JAXB

In my application user uploads several XMLs. Few XMLs that are uploaded do not contain a namespace tag and others contain it. I want to be able to support upload for both. JAXB is giving exception on former.
I want to able able to make namespace as optional ie support both files.
XML that is working
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<ns2:transforms xmlns:ns2="http://www.mynamesapace.com/xmlbeans/connectorconfig">
XML that is failing
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<transforms>
Here is how I am unmarshalling the XML
JAXBContext jaxbContext = JAXBContext.newInstance(Transforms.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
transforms = (Transforms) jaxbUnmarshaller.unmarshal(file);
This is my pojo
#XmlRootElement(name = "transforms", namespace =
"http://www.mynamesapace.com/xmlbeans/connectorconfig")
public class Transforms implements ConfigDiffable<Transforms,
ChangedTransforms> {
.....
Update :
If I remove
namespace =
"http://www.mynamesapace.com/xmlbeans/connectorconfig"
XML without namespace start working
Create a class:
class XMLReaderWithoutNamespace extends StreamReaderDelegate {
public XMLReaderWithoutNamespace(XMLStreamReader reader) {
super(reader);
}
#Override
public String getAttributeNamespace(int arg0) {
return "";
}
#Override
public String getNamespaceURI() {
return "";
}
}
Change your unmarshalling to:
JAXBContext jaxbContext = JAXBContext.newInstance(Transforms.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
InputStream is = new FileInputStream(file);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithoutNamespace xr = new XMLReaderWithoutNamespace(xsr);
transforms = (Transforms) jaxbUnmarshaller.unmarshal(xr);
I had no namespace defined in the pojo when I tested this.
Solution taken from this answer.

Java Unmarshalling issue (jaxb) [duplicate]

I am using JAXB to parse xml elements from the SOAP response. I have defined POJO classes for the xml elements. I have tested pojo classes without namespace and prefix its working fine .Though when i am trying to parse with namespaces and prefix facing the following exception.Requirement is to parse the input from SOAPMessage Object
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Envelope"). Expected elements are <{}Envelope>
Tried to fix by creating #XMLSchema for the package in package-info.java and located this file in package folder.Can any one guide me to move forward?
Referred this posts but didn help me .
EDITED :XMLSchema
#javax.xml.bind.annotation.XmlSchema (
xmlns = { #javax.xml.bind.annotation.XmlNs(prefix = "env",
namespaceURI="http://schemas.xmlsoap.org/soap/envelope/"),
#javax.xml.bind.annotation.XmlNs(prefix="ns3", namespaceURI="http://www.xxxx.com/ncp/oomr/dto/")
}
)
package com.one.two;
Thanks in advance
This can be done without modifying the generated JAXB code using standard SOAPMessage class. I wrote about this here and here
It's a little fiddly but works correctly.
Marshalling
Farm farm = new Farm();
farm.getHorse().add(new Horse());
farm.getHorse().get(0).setName("glue factory");
farm.getHorse().get(0).setHeight(BigInteger.valueOf(123));
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Marshaller marshaller = JAXBContext.newInstance(Farm.class).createMarshaller();
marshaller.marshal(farm, document);
SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
soapMessage.getSOAPBody().addDocument(document);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
soapMessage.writeTo(outputStream);
String output = new String(outputStream.toByteArray());
Unmarshalling
String example =
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Header /><soapenv:Body><ns2:farm xmlns:ns2=\"http://adamish.com/example/farm\"><horse height=\"123\" name=\"glue factory\"/></ns2:farm></soapenv:Body></soapenv:Envelope>";
SOAPMessage message = MessageFactory.newInstance().createMessage(null,
new ByteArrayInputStream(example.getBytes()));
Unmarshaller unmarshaller = JAXBContext.newInstance(Farm.class).createUnmarshaller();
Farm farm = (Farm)unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument());
Here is how you can handle your use cae:
If You Need to Map the Envelope Element
package-info
Typically you would use the #XmlSchema as follows. Using the namespace and elementFormDefault properties like I've done means that all data mapped to XML elements unless otherwise mapped will belong to the http://www.xxxx.com/ncp/oomr/dto/ namespace. The information specified in xmlns is for XML schema generation altough some JAXB implementations use this to determine the preferred prefix for a namespace when marshalling (see: http://blog.bdoughan.com/2011/11/jaxb-and-namespace-prefixes.html).
#XmlSchema (
namespace="http://www.xxxx.com/ncp/oomr/dto/",
elementFormDefault=XmlNsForm.QUALIFIED,
xmlns = {
#XmlNs(prefix = "env", namespaceURI="http://schemas.xmlsoap.org/soap/envelope/"),
#XmlNs(prefix="whatever", namespaceURI="http://www.xxxx.com/ncp/oomr/dto/")
}
)
package com.one.two;
import javax.xml.bind.annotation.*;
Envelope
If within the com.one.two you need to map to elements from a namespace other than http://www.xxxx.com/ncp/oomr/dto/ then you need to specify it in the #XmlRootElement and #XmlElement annotations.
package com.one.two;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Envelope", namespace="http://schemas.xmlsoap.org/soap/envelope/")
#XmlAccessorType(XmlAccessType.FIELD)
public class Envelope {
#XmlElement(name="Body", namespace="http://schemas.xmlsoap.org/soap/envelope/")
private Body body;
}
For More Information
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html
If You Just Want to Map the Body
You can use a StAX parser to parse the message and advance to the payload portion and unmarshal from there:
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;
public class UnmarshalDemo {
public static void main(String[] args) throws Exception {
XMLInputFactory xif = XMLInputFactory.newFactory();
StreamSource xml = new StreamSource("src/blog/stax/middle/input.xml");
XMLStreamReader xsr = xif.createXMLStreamReader(xml);
xsr.nextTag();
while(!xsr.getLocalName().equals("return")) {
xsr.nextTag();
}
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement<Customer> jb = unmarshaller.unmarshal(xsr, Customer.class);
xsr.close();
}
}
For More Information
http://blog.bdoughan.com/2012/08/handle-middle-of-xml-document-with-jaxb.html
Just wanted to add onto the existing answers -- while unmarshalling if the XML document is not namespace aware you might receive an error: javax.xml.bind.UnmarshalException: unexpected element (uri:"http://some.url";, local:"someOperation")
If this is the case you can simply use a different method on the unmarshaller:
Unmarshaller unmarshaller = JAXBContext.newInstance(YourObject.class).createUnmarshaller();
JAXBElement<YourObject> element = unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument(), YourObject.class);
YourObject yo = element.getValue();

JAXB: unmarshal from XML subtree?

My JAVA program uses an internal class hierarchy which resembles GPX 1.1, but is not identical. since rewriting it to conform 1:1 to GPX is a huge effort, I'd like to change it bit by bit, i.e. reading the <metadata> subtree into the Class MetadataType as generated with xjc from the XSD file
the remaining GPX file is parsed with DOM, until <metadata> shows up:
private void parseMetadata(MetadataType metadata, Element element) throws JAXBException {
try {
System.out.println(element.getNodeName()); // output: metadata
JAXBContext context = JAXBContext.newInstance(MetadataType.class);
javax.xml.bind.Unmarshaller u = context.createUnmarshaller();
JAXBElement<MetadataType> meta = u.unmarshal(element, MetadataType.class);
metadata = meta.getValue();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(metadata.getName()); // NULL
System.out.println(metadata.getAuthor().getName()); // NULL
}
throws
javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"metadata"). Expected elements are <{http://www.topografix.com/GPX/1/1}gpx>
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:647)
the class looks like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "metadataType", propOrder = {
"name",
"desc",
"author",
"copyright",
"link",
"time",
"keywords",
"bounds",
"extensions"
})
#XmlRootElement(name = "metadata")
public class MetadataType {
protected String name = "";
protected String desc = "";
protected PersonType author;
protected CopyrightType copyright = new CopyrightType();
protected List<LinkType> link = new ArrayList<LinkType>();
#XmlSchemaType(name = "Date")
protected Date time;
protected String keywords = "";
protected BoundsType bounds = new BoundsType();
protected ExtensionsType extensions = new ExtensionsType();
[...]
}
is it possible to unmarshal from an XML subtree; and if yes, what am I doing wrong?
UPDATE/1:
thanks to lexicore, I'm a step further: element definitely contains the metadata node, #XmlRootElement is set, unmarshaling now with unmarshal(element, MetadataType.class).
unmarshaling works, but the content of the objects is empty. I wonder if I run into some namespace troubles here?
the package com.topografix.gpx._1._1 contains a package-info.java, generated by xjc:
#javax.xml.bind.annotation.XmlSchema(namespace = "http://www.topografix.com/GPX/1/1", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.topografix.gpx._1._1;
here's one of the test files:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1"
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
xmlns:wptx1="http://www.garmin.com/xmlschemas/WaypointExtension/v1"
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
creator="GPSMAP 62s" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1
http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas /GpxExtensions/v3
http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1
http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1
http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">
<metadata>
<link href="http://www.garmin.com">
<text>Garmin International</text>
</link>
<time>2014-01-01T22:26:49Z</time>
</metadata>
[...]
UPDATE/2: the namespace/package name is right. If the namespace is set to (i.e.) "foobar", the following exception is thrown:
javax.xml.bind.JAXBException: Provider com.sun.xml.internal.bind.v2.ContextFactory could not be instantiated:
javax.xml.bind.JAXBException: "foobar" doesnt contain ObjectFactory.class or jaxb.index
when initialized as before, the exception is not thrown, meaning that the namespace is correct AND the ObjectFactory.class is found.
String contextPath = MetadataType.class.getPackage().getName();
JAXBContext context = JAXBContext.newInstance(contextPath);
So, the namespace is correct, but somehow the "link" to the MetadataType class is missing?
I guess you have a few problems here:
Your MetadataType does not have the #XmlRootElement. So JAXB does not know which element is supposed to match it.
You want to unmarshal partially, but you're unmarshalling (I guess) the whole document.
What you could try:
Try unmarshalling a specific class from the specific node using the unmarshal(node, class) method
From your stack trace, you try to unmarshal gpx:gpx element, not the metadata element. You have to go deeper
You have to overtake package-info.java as well (or provide namespaces in your MetadataType, otheriwse you're missing namespaces
See this answer about partial unmarshalling.

Validate an XML File Against Multiple Schema Definitions

I'm trying to validate an XML file against a number of different schemas (apologies for the contrived example):
a.xsd
b.xsd
c.xsd
c.xsd in particular imports b.xsd and b.xsd imports a.xsd, using:
<xs:include schemaLocation="b.xsd"/>
I'm trying to do this via Xerces in the following manner:
XMLSchemaFactory xmlSchemaFactory = new XMLSchemaFactory();
Schema schema = xmlSchemaFactory.newSchema(new StreamSource[] { new StreamSource(this.getClass().getResourceAsStream("a.xsd"), "a.xsd"),
new StreamSource(this.getClass().getResourceAsStream("b.xsd"), "b.xsd"),
new StreamSource(this.getClass().getResourceAsStream("c.xsd"), "c.xsd")});
Validator validator = schema.newValidator();
validator.validate(new StreamSource(new StringReader(xmlContent)));
but this is failing to import all three of the schemas correctly resulting in cannot resolve the name 'blah' to a(n) 'group' component.
I've validated this successfully using Python, but having real problems with Java 6.0 and Xerces 2.8.1. Can anybody suggest what's going wrong here, or an easier approach to validate my XML documents?
So just in case anybody else runs into the same issue here, I needed to load a parent schema (and implicit child schemas) from a unit test - as a resource - to validate an XML String. I used the Xerces XMLSchemFactory to do this along with the Java 6 validator.
In order to load the child schema's correctly via an include I had to write a custom resource resolver. Code can be found here:
https://code.google.com/p/xmlsanity/source/browse/src/com/arc90/xmlsanity/validation/ResourceResolver.java
To use the resolver specify it on the schema factory:
xmlSchemaFactory.setResourceResolver(new ResourceResolver());
and it will use it to resolve your resources via the classpath (in my case from src/main/resources). Any comments are welcome on this...
http://www.kdgregory.com/index.php?page=xml.parsing
section 'Multiple schemas for a single document'
My solution based on that document:
URL xsdUrlA = this.getClass().getResource("a.xsd");
URL xsdUrlB = this.getClass().getResource("b.xsd");
URL xsdUrlC = this.getClass().getResource("c.xsd");
SchemaFactory schemaFactory = schemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
//---
String W3C_XSD_TOP_ELEMENT =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" elementFormDefault=\"qualified\">\n"
+ "<xs:include schemaLocation=\"" +xsdUrlA.getPath() +"\"/>\n"
+ "<xs:include schemaLocation=\"" +xsdUrlB.getPath() +"\"/>\n"
+ "<xs:include schemaLocation=\"" +xsdUrlC.getPath() +"\"/>\n"
+"</xs:schema>";
Schema schema = schemaFactory.newSchema(new StreamSource(new StringReader(W3C_XSD_TOP_ELEMENT), "xsdTop"));
The schema stuff in Xerces is (a) very, very pedantic, and (b) gives utterly useless error messages when it doesn't like what it finds. It's a frustrating combination.
The schema stuff in python may be a lot more forgiving, and was letting small errors in the schema go past unreported.
Now if, as you say, c.xsd includes b.xsd, and b.xsd includes a.xsd, then there's no need to load all three into the schema factory. Not only is it unnecessary, it will likely confuse Xerces and result in errors, so this may be your problem. Just pass c.xsd to the factory, and let it resolve b.xsd and a.xsd itself, which it should do relative to c.xsd.
From the xerces documentation :
http://xerces.apache.org/xerces2-j/faq-xs.html
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
...
StreamSource[] schemaDocuments = /* created by your application */;
Source instanceDocument = /* created by your application */;
SchemaFactory sf = SchemaFactory.newInstance(
"http://www.w3.org/XML/XMLSchema/v1.1");
Schema s = sf.newSchema(schemaDocuments);
Validator v = s.newValidator();
v.validate(instanceDocument);
I faced the same problem and after investigating found this solution. It works for me.
Enum to setup the different XSDs:
public enum XsdFile {
// #formatter:off
A("a.xsd"),
B("b.xsd"),
C("c.xsd");
// #formatter:on
private final String value;
private XsdFile(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
Method to validate:
public static void validateXmlAgainstManyXsds() {
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
String xmlFile;
xmlFile = "example.xml";
// Use of Enum class in order to get the different XSDs
Source[] sources = new Source[XsdFile.class.getEnumConstants().length];
for (XsdFile xsdFile : XsdFile.class.getEnumConstants()) {
sources[xsdFile.ordinal()] = new StreamSource(xsdFile.getValue());
}
try {
final Schema schema = schemaFactory.newSchema(sources);
final Validator validator = schema.newValidator();
System.out.println("Validating " + xmlFile + " against XSDs " + Arrays.toString(sources));
validator.validate(new StreamSource(new File(xmlFile)));
} catch (Exception exception) {
System.out.println("ERROR: Unable to validate " + xmlFile + " against XSDs " + Arrays.toString(sources)
+ " - " + exception);
}
System.out.println("Validation process completed.");
}
I ended up using this:
import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
.
.
.
try {
SAXParser parser = new SAXParser();
parser.setFeature("http://xml.org/sax/features/validation", true);
parser.setFeature("http://apache.org/xml/features/validation/schema", true);
parser.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
parser.setProperty("http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", "http://your_url_schema_location");
Validator handler = new Validator();
parser.setErrorHandler(handler);
parser.parse("file:///" + "/home/user/myfile.xml");
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException ex) {
e.printStackTrace();
}
class Validator extends DefaultHandler {
public boolean validationError = false;
public SAXParseException saxParseException = null;
public void error(SAXParseException exception)
throws SAXException {
validationError = true;
saxParseException = exception;
}
public void fatalError(SAXParseException exception)
throws SAXException {
validationError = true;
saxParseException = exception;
}
public void warning(SAXParseException exception)
throws SAXException {
}
}
Remember to change:
1) The parameter "http://your_url_schema_location" for you xsd file location.
2) The string "/home/user/myfile.xml" for the one pointing to your xml file.
I didn't have to set the variable: -Djavax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema=org.apache.xerces.jaxp.validation.XMLSchemaFactory
Just in case, anybody still come here to find the solution for validating xml or object against multiple XSDs, I am mentioning it here
//Using **URL** is the most important here. With URL, the relative paths are resolved for include, import inside the xsd file. Just get the parent level xsd here (not all included xsds).
URL xsdUrl = getClass().getClassLoader().getResource("my/parent/schema.xsd");
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(xsdUrl);
JAXBContext jaxbContext = JAXBContext.newInstance(MyClass.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema(schema);
/* If you need to validate object against xsd, uncomment this
ObjectFactory objectFactory = new ObjectFactory();
JAXBElement<MyClass> wrappedObject = objectFactory.createMyClassObject(myClassObject);
marshaller.marshal(wrappedShipmentMessage, new DefaultHandler());
*/
unmarshaller.unmarshal(getClass().getClassLoader().getResource("your/xml/file.xml"));
If all XSDs belong to the same namespace then create a new XSD and import other XSDs into it. Then in java create schema with the new XSD.
Schema schema = xmlSchemaFactory.newSchema(
new StreamSource(this.getClass().getResourceAsStream("/path/to/all_in_one.xsd"));
all_in_one.xsd :
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ex="http://example.org/schema/"
targetNamespace="http://example.org/schema/"
elementFormDefault="unqualified"
attributeFormDefault="unqualified">
<xs:include schemaLocation="relative/path/to/a.xsd"></xs:include>
<xs:include schemaLocation="relative/path/to/b.xsd"></xs:include>
<xs:include schemaLocation="relative/path/to/c.xsd"></xs:include>
</xs:schema>

Categories