I am trying to validate xml files in a junit unit test. This is my simplified code. Schemas are accessible from http://www.unece.org/ and http://www.gs1.org/
package test;
import java.net.URL;
import java.nio.file.Paths;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class Epcis11MessageCreatorForSoTest {
private static Schema schema;
private final static String[] XSD_FILES = {
"epcis11/xsd/BasicTypes.xsd",
"epcis11/xsd/DocumentIdentification.xsd",
"epcis11/xsd/Partner.xsd",
"epcis11/xsd/Manifest.xsd",
"epcis11/xsd/BusinessScope.xsd",
"epcis11/xsd/StandardBusinessDocumentHeader.xsd",
"epcis11/xsd/EPCglobal.xsd",
"epcis11/xsd/EPCglobal-epcis-1_1.xsd",
"epcis11/xsd/EPCglobal-epcis-query-1_1.xsd",
"epcis11/xsd/EPCglobal-epcis-masterdata-1_1.xsd",
};
#BeforeClass
public static void beforeClass() throws Exception {
try {
System.setProperty("jaxp.debug", "10");
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Source[] sources = new Source[XSD_FILES.length];
int i = 0;
for (String xsdfile : XSD_FILES) {
URL resource = Epcis11MessageCreatorForSoTest.class.getClassLoader().getResource(xsdfile);
String systemId = Paths.get(resource.toURI()).toFile().getAbsolutePath();
StreamSource ss = new StreamSource(
Epcis11MessageCreatorForSoTest.class.getClassLoader().getResourceAsStream(xsdfile),systemId);
sources[i] = ss;
i++;
}
schema = schemaFactory.newSchema(sources);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
#Test
public void testFoo() {
Assert.assertTrue(true);
}
}
However the schema is not being built due to:
target/test-classes/epcis11/xsd/EPCglobal-epcis-1_1.xsd; lineNumber: 46; columnNumber: 60; src-resolve: Cannot resolve the name 'sbdh:StandardBusinessDocumentHeader' to a(n) 'element declaration' component.
Relevant lines look like this (EPCglobal-epcis-1_1.xsd)
<xsd:schema xmlns:epcis="urn:epcglobal:epcis:xsd:1" xmlns:sbdh="http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader" xmlns:epcglobal="urn:epcglobal:xsd:1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:epcglobal:epcis:xsd:1" elementFormDefault="unqualified" attributeFormDefault="unqualified" version="1.1">
...
<xsd:import namespace="http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader" schemaLocation="./StandardBusinessDocumentHeader.xsd"/>
...
<xsd:element ref="sbdh:StandardBusinessDocumentHeader"/>
I use Eclipse as an ide and project uses JavaSE-1.7 as JRE System Library. When looking at these xsd-files they are syntactically ok. That is I do not get any xml-related errors in ide. Exactly same error appears when test is either run from within ide or through maven.
Any suggestions about what might be wrong with my schemafactory or xsd sources as java objects?
I was lucky to have some time to run my code through a debugger. It was pretty heavy stuff. For example com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.class contains over 100 imports and over 4000 lines of code.
Anyhow, I am willing to think that there is a programming error in com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadGrammar(XMLInputSource) that loads xsd grammars, or schemas or schemasources in our context.
First grammar loaded for http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader namespace is BasicTypes.xsd.
My findings are that, that grammar is mapped by its namespace and after it is included by other schemas it somehow is prevententing the includers', that share the namespace, grammars from being mapped. I can succesfully refer from EPCglobal-epcis-1_1.xsd to any type defined in BasicTypes.xsd, but referring to any other element or type in http:/ /www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader namespace that is not defined in BasicTypes.xsd results in a name resolving error.
My theory is backed up by that, if I move any type definition or element declaration to BasicTypes.xsd I can succesfully refer from EPCglobal-epcis-1_1.xsd to that type or element .
As a matter of fact I can, unwillingly, consolidate all type definitions and element declarations in namespace http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader that are used in this project into one xsd source file and then the schema is built correctly.
But since none of the xsd-files used for validation is maintained by me this feels like some sort of hack. I would rather use a system that can build an xml schema from my project files as they are.
Related
I'm trying to do something which I thought was pretty simple, which is to use JSON for persisting a POJO container with various properties in it.
After reading up about various ways to marshal/unmarshal JSON without all the web services stuff on top, I figured MOXy would be the best way as it seems to be able to handle non-JAXB annotated POJOs. I'm using MOXy 2.7.3.
However, I ran into two issues:
I'm using Maven with the following dependency:
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>2.7.3</version>
</dependency>
However, if I don't also include this dependency on a GlassFish JAR:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.1.2</version>
</dependency>
I get the following exception when attempting to unmarshal (marshaling works fine):
Caused by: Exception [EclipseLink-25004] (Eclipse Persistence Services - 2.7.3.v20180807-4be1041): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred unmarshalling the document
Internal Exception: javax.json.JsonException: Provider org.glassfish.json.JsonProviderImpl not found
at org.eclipse.persistence.exceptions.XMLMarshalException.unmarshalException(XMLMarshalException.java:122)
at org.eclipse.persistence.internal.oxm.record.json.JsonStructureReader.parse(JsonStructureReader.java:148)
...
Why does MOXy need the GlassFish JsonProviderImpl dependency?
I need to declare an #XmlRootElement in my container class. Not sure why, but this is the case even if I use marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);, which seems to be the default. If I don't don't have an #XmlRootElement I get the following exception when trying to unmarshal (marshaling works fine):
Caused by: Exception [EclipseLink-25008] (Eclipse Persistence Services - 2.7.3.v20180807-4be1041): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: A descriptor with default root element stringValue was not found in the project
Maybe I'm just confused, but it seemed like from the docs that MOXy was able to work with POJOs?
Here is some example code showing what I'm doing:
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.MarshallerProperties;
public class MoxyTest
{
public static void main(String[] args) throws Exception
{
// Create an instance of the container to be written/read with JSON
TestContainer testContainer = new TestContainer();
testContainer.stringValue = "test string";
testContainer.intValue = 5;
testContainer.zonedDateTimeValue = ZonedDateTime.now().minusMonths(1);
// Note that JAXBContextFactory is specific to MOXy, this was done to avoid having to create a jaxb.properties
// See https://stackoverflow.com/questions/6963996/can-i-replace-jaxb-properties-with-code and
// https://stackoverflow.com/questions/28676613/set-moxy-as-jaxb-provider-without-properties-file-in-the-same-package
JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[] {TestContainer.class}, null);
Marshaller marshaller = jaxbContext.createMarshaller();
// By default the marshaller will output XML, we have to tell it to use JSON instead
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
File dataFile = Paths.get("/temp", "datafile.json").toFile();
Writer writer = new FileWriter(dataFile);
marshaller.marshal(testContainer, writer);
writer.close();
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
// By default the unmarshaller will try to read XML, we have to tell it to use JSON instead
unmarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
// Determine the location for the file which is used to persist the data
if (dataFile.exists() && (dataFile.length() > 0))
{
Reader reader = new FileReader(dataFile);
TestContainer readTestContainer = (TestContainer) unmarshaller.unmarshal(reader);
System.out.println(readTestContainer.intValue);
System.out.println(readTestContainer.stringValue);
System.out.println(readTestContainer.zonedDateTimeValue);
reader.close();
}
}
}
And the test container (with #XmlRootElement):
import java.time.ZonedDateTime;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "testContainer")
public class TestContainer
{
public String stringValue;
public int intValue;
public ZonedDateTime zonedDateTimeValue;
}
Note that for extra awesomeness, maybe someone can answer why ZonedDateTime will properly marshal but not unmarshal (result is null)? Here are the results from running MoxyTest:
5
test string
null
I am trying to use JAXB with fields of the LocalDateTime type. I wrote an adapter to handle conversion:
public class LocalDateTimeXmlAdapter extends XmlAdapter<String, LocalDateTime> {
#Override
public String marshal(LocalDateTime arg0) throws Exception {
return arg0.toString();
}
#Override
public LocalDateTime unmarshal(String arg) throws Exception {
return LocalDateTime.parse(arg);
}
}
I registered the adapter in package-info.java like so:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type=LocalDateTime.class, value=LocalDateTimeXmlAdapter.class)
})
package xml;
import java.time.LocalDateTime;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
This seems to be sufficient according to this page.
However, I keep getting the following error:
com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
java.time.LocalDateTime does not have a no-arg default constructor.
I understand the reason for the exception being thrown, but I can hardly add a default constructor to java.time.LocalDateTime. This seems to be a shortcoming of the class / a strange design decision. Are there any workarounds?
What you have should work. One of the following may be wrong:
Since you have specified the #XmlJavaTypeAdapter at the package level it will only apply to properties on classes in your package called xml. Is there a class in your model from a different package that has a mapped property of type LocalDateTime?
It is also possible that your package-info.java file is not being compiled.
Had same behaviour: IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions.
My pbm was: I have several packages (three) where the package-info.java file is needed, like shown in the following picture.
I "solved" this pbm by adding a package-info.java in each of the three directories. Example for package fr.gouv.agriculture.dal.ct.planCharge.metier.dao.charge.xml:
#XmlJavaTypeAdapter(type = LocalDate.class, value = LocalDateXmlAdapter.class)
package fr.gouv.agriculture.dal.ct.planCharge.metier.dao.charge.xml;
If someone has a better idea than copy/paste into several package-info.java files, thanks in advance.
I wrote a bean (BaseBeanEx) extending a JAXB annotated bean (BaseBean). The BaseBean is in a List somewhere in the datastructure and can't be changed. The Software does an explicit cast to BaseBeanEx whenever it is needed. I also wrote an ObjectFactory to create BaseBeanEx instead of BaseBean. This all works fine, but now I added a afterUnmarshal method to BaseBeanEx which never gets called.
Is this a bug or is this according to the specs? If later is the case, is there some elegant work around?
I'm using the default JAXB engine.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
The reason that afterUnmarshal is not being called on BaseBeanEx is that the metadata was built on the BaseBean class. To get your use case to work you need to let your JAXB impl know that you really want to map to instances of BaseBeanEx.
OPTION #1 - Any JAXB Implementation using Annotations
Root
You can use the #XmlElement annotation to override the type of a field/property. In the example below the signature of the method is List<BaseBean>, but the #XmlElement annotation informs the JAXB implementation the property should be interpreted as List<BaseBeanEx>.
package forum10174513;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
private List<BaseBean> baseBeans;
#XmlElement(name="base-bean", type=BaseBeanEx.class)
public List<BaseBean> getBaseBeans() {
return baseBeans;
}
public void setBaseBeans(List<BaseBean> baseBeans) {
this.baseBeans = baseBeans;
}
}
OPTION #2 - Using MOXy's External Mapping Document
The BaseBean is in a List somewhere in the datastructure and can't be
changed.
If you can't modify your domain model and are using MOXy as your JAXB provider then you can leverage its external mapping document to apply metadata without modifying your domain model.
bindings.xml
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum10174513">
<java-types>
<java-type name="Root">
<java-attributes>
<xml-element
java-attribute="baseBeans"
name="base-bean"
type="forum10174513.BaseBeanEx"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Demo
Below is some code that demonstrates how to bootstrap a JAXBContext that leverages the external mapping document. There is currently a bug where classes only referenced through the external mapping document won't have there event methods registered (http://bugs.eclipse.org/376876). You can work around this issue by explicitly including this class in the list of classes used to create the JAXBContext.
package forum10174513;
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum10174513/bindings.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Root.class, BaseBeanEx.class}, properties);
File xml = new File("src/forum10174513/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Root root = (Root) unmarshaller.unmarshal(xml);
}
}
BaseBean
package forum10174513;
public class BaseBean {
}
BaseBeanEx
package forum10174513;
import javax.xml.bind.Unmarshaller;
public class BaseBeanEx extends BaseBean {
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
System.out.println("AFTER UNMARSHAL WAS CALLED");
}
}
Output
Below is the output that was generated by running the demo code.
AFTER UNMARSHAL WAS CALLED
AFTER UNMARSHAL WAS CALLED
For More Information
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
http://blog.bdoughan.com/2011/05/jaxb-and-interface-fronted-models.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
did you spell it right? the method name is "afterUnmarshal" (one 'L'). Specs
UPDATE:
thinking about it some more, jaxb probably never finds out about the callback because it doesn't know about your custom subclass. i would assume that JAXB examines all the classes during JAXBContext setup. at that point, JAXB only knows about the base bean class, not your custom subclass, and therefore never finds the callback methods.
2 thoughts. you could use the "external callback" mechanism (use a separate event handler which does what you need for your custom classes). or, you could try to generate (or add later) the base bean classes with the callback methods. then JAXB will probably recognize and call the methods, which you can then override in your custom subclass.
I am trying to create java objects from xml file. I am using jaxb(unmarshalling) to create java objects.I am getting errors javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.w3.org/2001/XMLSchema", local:"schema"). Expected elements are
I did some google and found out that, we need xsd file to do that... so I converted it to xsd using apache inst2xsd tool. I am using following java code:
import java.io.FileNotFoundException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="report")
public class Report
{
public static void main(String [] args) throws FileNotFoundException
{
try
{
JAXBContext jc = JAXBContext.newInstance(new Class[] {com.bcbsks.testjb.Report.class});
Unmarshaller um = jc.createUnmarshaller();
Report myJAXBObject = (Report)um.unmarshal(new java.io.FileInputStream("report.xsd"));
}
catch( UnmarshalException ue )
{
ue.printStackTrace();
}
catch( JAXBException je )
{
je.printStackTrace();
}
}
}
But I am getting fol;owing error:
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.w3.org/2001/XMLSchema", local:"schema"). Expected elements are (none)
Can you please tell me whats wrong I am doing?
Any help is greatly appreciated.
I think you are missing a few steps. You didn't post what report.xsd is, nor a sample xml, so I'm going to take a few guesses.
For starters, you are trying to unmarshal the xsd and not xml, which is itself the root of the problem. That being said, your Report.java class does not look properly generated so it is unlikely that your unmarshalling would work even if you tried against your xml file.
If you have a properly created XSD file, the first thing you should do is create the JaxB POJOs using xjc. xjc comes installed with java, and you use it to create annotated java classes from the xsd. It will also create 2 additional files - ObjectFactory.java and package-info.java which are used by JAXB. (You can specify the output path using the -d param (see --help for the full list of switches)
xjc -d c:\dev\myproject\src\main\java report.xsd
Once you have those files generated, you have to create your JAXBContext based on that package/file.
JAXBContext jc = JAXBContext.newInstance(something.generated.Report.class);
Unmarshaller um = jc.createUnmarshaller();
Report myJAXBObject = (Report)um.unmarshal(new java.io.FileInputStream("report.xsd"), Report.class).getValue();
The unmarshaller generates a JAXBElement, from which you can extract the actual report class.
Hope this helps.
There are no properties on the bean you are trying to unmarshal. But more importantly, you are trying to deserialize your object from the XSD itself. The error message is a good indicator here:
unexpected element (uri:"http://www.w3.org/2001/XMLSchema", local:"schema")
JAXB is spitting out this error message because it is attempting to map the XSD's metadata to properties of your bean. Which of course, your bean doesn't actually have any. The next part of the error message indicates as much:
Expected elements are (none)
You need to define your Java Bean properly (put some properties on it!), and actually get an XML file that represents the serialized version of your bean.
How do I validate an XML document that I already have in memory as a DOM Document?
You can use the javax.xml.validation APIs to validate XML in memory. Below is an example of using these APIs with a JAXBSource, to validate a DOM model simply use a DOMSource.
package blog.validation;
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.util.JAXBSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
public class Demo {
public static void main(String[] args) throws Exception {
Customer customer = new Customer();
customer.setName("Jane Doe");
customer.getPhoneNumbers().add(new PhoneNumber());
customer.getPhoneNumbers().add(new PhoneNumber());
customer.getPhoneNumbers().add(new PhoneNumber());
JAXBContext jc = JAXBContext.newInstance(Customer.class);
JAXBSource source = new JAXBSource(jc, customer);
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("customer.xsd"));
Validator validator = schema.newValidator();
validator.setErrorHandler(new MyErrorHandler());
validator.validate(source);
}
}
For More Information
http://blog.bdoughan.com/2010/11/validate-jaxb-object-model-with-xml.html
How do you made up your model? I have a solution at work where i get an XML message in text format which i parse using xmlbeans. Then i have the ability to call a validate method on it. So there is a Java class compiled during my maven build which reflects the XSD i have.
There's nothing special about it. The javax.xml.validation validators take a Source. Check the constructors of the implementing classes of Source.