Need help resolving a JAXB unmarshall issue - java

I am having the typical 'unexpected element (uri:"", local:"stickynote")' JAXB error, although it only shows when I set the event handler on the unmarshaller object. When the event handler is not set code runs fine, but the specific data needed is not unmarshalled.
I have worked on the problem for a couple of days, including many Internet searches, and am not hitting on the right solution. I feel like I am missing something simple, but so far it is eluding me.
Notes:
I am using Java JDK 8 with Apache NetBeans 15.
The original code was generated with the xjc command from schemas we receive from multiple vendors. I cannot modify the schemas and have to figure out how to use them "as is".
I wrote the code below to duplicate the issue and simplify the posted code. Import statements have been removed for brevity. If they are needed please let me know.
Since inheritance is involved in one of the classes, I added the appropriate #XmlSeeAlso annotation. That did not help.
This is my first real foray into JAXB. It seems to be perfect for this project. I just have to figure out how to get it to work.
First, example XML:
<?xml version="1.0" encoding="UTF-8"?>
<documents>
<document>
<notes>
<stickynote id="123">test note</stickynote>
</notes>
</document>
</documents>
Code to create the JAXB context and create the Unmarshaller object:
JAXBContext context = JAXBContext.newInstance( Documents.class );
Unmarshaller u = context.createUnmarshaller();
u.setEventHandler(new DefaultValidationEventHandler());
JAXBElement< Documents > root =
u.unmarshal(
new StreamSource( new File( FILENAME )),
Documents.class );
The corresponding classes to handle each element:
Documents class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "document" })
#XmlRootElement(name = "documents", namespace = "http://www.example.org/documents")
public class Documents {
protected Document document;
public Document getDocument() {
return document;
}
public void setDocument(Document document) {
this.document = document;
}
}
Document class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "notes" })
#XmlRootElement(name = "document", namespace = "http://www.example.org/documents")
public class Document {
protected NotesType notes;
public NotesType getNotes() {
return notes;
}
public void setNotes(NotesType notes) {
this.notes = notes;
}
}
NotesType class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "NotesType", propOrder = { "notes" })
public class NotesType {
protected List<NoteType> notes;
public List<NoteType> getNotes() {
if ( isNull( notes )) {
notes = new ArrayList<>();
}
return this.notes;
}
}
NoteType class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "NoteType", propOrder = { "note" })
#XmlSeeAlso({ StickyNote.class })
public class NoteType {
#XmlAttribute(name = "id", required = true)
protected String id;
protected String note;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
And finally, the StickyNote class, which extends the NoteType class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "subType" })
public class StickyNote extends NoteType {
protected String subType = "sticky";
public String getSubType() {
return subType;
}
public void setSubType(String subType) {
this.subType = subType;
}
}
The exception is:
DefaultValidationEventHandler: [ERROR]: unexpected element (uri:"", local:"stickynote"). Expected elements are <{}notes>
Location: line 5 of file:/C:/Test/documents.xml
javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"stickynote"). Expected elements are <{}notes>
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:726)
*and so on*
Thanks in advance for any and all suggestions and help!
john

My issue turned out to be how I was using xjc. I was building each schema we received individually, not realizing there was some overlap in the schemas due to the vendors using other vendors schemas. This caused issues when one vendor's schema was built early in the process and then another vendor's schema was built after it that used the earlier vendor's schema.
Building all the schemas as a group, instead of individually, allowed xjc to "see" everything it needed to see and the corresponding code worked as expected.

Related

Jackson - Marshall XML to POJO Unrecognized field error

I have a response from a RESTful XML webservice, stored as a string.
String responseAsStr = response.readEntity(String.class);
Im trying to map the response to an object I generated using a schema.
XmlMapper xmlMapper = new XmlMapper();
ResponseType responseAsObj = xmlMapper.readValue(responseAsStr, ResponseType.class);
When executing the line xmlMapper.readValue(...) I get this error :
Unrecognized field "ResponseType" (class blah.bleh.smth.ResponseType), not marked as ignorable (22 known properties: "responseType" ...)
The generated class containing the problem field looks like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"responseType",
...
})
#XmlRootElement(name = "SomeClassElement")
public class SomeClassElement {
#XmlElement(name = "ResponseType")
protected ResponseType responseType;
...
public ResponseType getResponseType() {
return responseType;
}
public void setErrorDetail(ResponseType value) {
this.responseType = value;
}
...
}
The problem field class looks like:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"1",
"2"
})
public static class ResponseType {
#XmlElement(name = "1", required = true)
protected String 1;
#XmlElement(name = "2", required = true)
protected String 2;
/**
The incoming XML Looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SomeClassElement xmlns="redacted">
<ResponseType>
<1>BlahBlah</1>
<2>BlahBlah</2>
</ResponseType>
</SomeClassElement>
Has anyone encountered this before? It seems as if Jackson is unable to identify the field due to the lack of capitalisation in the "known properties" array (responseType as opposed to ResponseType). I altered the #XmlType decorator to see if changing capitalization fixed the issue here but alas the error persists.

Why my jaxb2marshaller does not unmarshal what it marshalled before?

Im struggling with the jaxb2marshaller configuration to work with classes generated from wsdl file.
My jaxb2marshaller
public Jaxb2Marshaller marshaller() {
final Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPaths("org.otheruri", "org.tempuri");
return marshaller;
}
I prepare a structure of classes and pass them thru marshaller.marshal to get the xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:ValidateResponse xmlns="http://otheruri.org" xmlns:ns2="http://tempuri.org/">
<ns2:ValidateResult>
<ArrayOfPerson>
<Person><NawNumber>personNAW</NawNumber></Person>
</ArrayOfPerson>
</ns2:ValidateResult>
</ns2:ValidateResponse>
But when I take this xml and run marshaller.unmarshall the ValidateResult.arrayOfPerson is null. There is no stacktrace or anything.
My generated classes look like this
package org.tempuri
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"validateResult"
})
#XmlRootElement(name = "ValidateResponse")
public class ValidateResponse {
#XmlElementRef(name = "ValidateResult", namespace = "http://tempuri.org/", type = JAXBElement.class, required = false)
protected JAXBElement<org.otheruri.ValidateResponse> validateResult;
public JAXBElement<org.otheruri.ValidateResponse> getValidateResult() {
return validateResult;
}
public void setValidateResult(JAXBElement<org.otheruri.ValidateResponse> value) {
this.validateResult = value;
}
}
and
package org.otheruri
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "ValidateResponse", propOrder = {
"persons",
"userLoggedIn"
})
public class ValidateResponse {
#XmlElementRef(name = "Persons", namespace = "http://otheruri.org", type = JAXBElement.class, required = false)
protected JAXBElement<ArrayOfPerson> persons;
#XmlElementRef(name = "UserLoggedIn", namespace = "http://otheruri.org", type = JAXBElement.class, required = false)
protected JAXBElement<ValidateCredentialsResponse> userLoggedIn;
public JAXBElement<ArrayOfPerson> getPersons() {
return persons;
}
public void setPersons(JAXBElement<ArrayOfPerson> value) {
this.persons = value;
}
public JAXBElement<ValidateCredentialsResponse> getUserLoggedIn() {
return userLoggedIn;
}
public void setUserLoggedIn(JAXBElement<ValidateCredentialsResponse> value) {
this.userLoggedIn = value;
}
}
EDIT
So after time and lots of debbuging I know what is wrong. For unknown reason the created xml has ArrayOfPerson node (like class name) instead of Persons (that is specified in #XmlElementRef). Still I dont know why this is happening
So in the end it appeared that my generated object factory had 2 methods that returned same JaxbObject and accepted ArrayOfPerson object and very similar name, but had different name specified in the #XmlElementDecl, and of course I used the wrong one to generate xml, so I could not consume it later. :/

JAXB - how to unmarshal an entity into list of entities with size 1

I have the following JAXB entity:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNER)
public class FilePartner
{
#XmlAttribute(name = EntityConstants.IDENTIFIER, required = true)
private String identifier;
#XmlElement(name = EntityConstants.NAME)
private String name;
#XmlElement(name = EntityConstants.ROOT_PATH)
private String rootPath;
...
}
which serialized into a similar structure:
<file-partner identifier="foo">
<name>bar</name>
<root-path>path123</root-path>
...
</file-partner>
I also have an entity which represents a list of partners:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNERS)
public class FilePartnerList
{
#XmlElement(name = EntityConstants.PARTNER)
private List<FilePartner> partners = new ArrayList<FilePartner>();
public List<FilePartner> getPartners()
{
return partners;
}
public void addPartners(List<FilePartner> partners)
{
this.partners.addAll(partners);
}
}
which serializes into:
<partners>
<file-partner identifier="foo">
...
</file-partner>
<file-partner identifier="foo2">
...
</file-partner>
...
</partners>
I am looking for a way to force the jaxb unmarshaller to deserialize XMLs in the form of
<file-partner identifier="foo">
<name>bar</name>
<root-path>path123</root-path>
...
</file-partner>
into FilePartnerList instances with list size of 1, i.e:
JAXBContext context = JAXBContext.newInstance(FilePartner.class, FilePartnerList.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
InputStream inputStream = getResourceAsStream(filePartnerAsXml);
FilePartnerList partnerList = (FilePartnerList) unmarshaller.unmarshal(inputStream); // This SHOULD be unmarshalled to FilePartnerList instead of FilePartner
assertTrue(partnerList.getPartners().getSize().equals(1));
How do I achieve that?
private List<FilePartner> partners = new ArrayList<FilePartner>(**1**);
That way you'll get a fixed size input array....
But I'm sure that you will want to get the XSD of that model with the 'maxocurrs=1' so you will end modifying the XSD manually.
Anyway: why don't you, if the list size must be fixed to '1', simply set it as a simple node with a single child? Something like this (untested):
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNERS)
public class FilePartnerList
{
#XmlElement(name = EntityConstants.PARTNER)
private FilePartner partners;
public FilePartner getFilePartner()
{
return partner;
}
public void setPartner(FilePartner partner)
{
this.partner = partner;
}
}
This way you will have one and only one partner per parnets-list.
The XML that fullfits the XSD of your service a text and in that text is indistinguible a list with max size 1 and a node.

Jersey Client can not send/receive XML messages with JAXB Moxy serialization?

I'm trying to use the jersey-client to make some RESTful requests with XML messages. I don't want to serve any endpoints so there are no jersey-server packages involved.
For the testing purposes I'm using the publicly reachable http://www.thomas-bayer.com/sqlrest/CUSTOMER testing service.
As stated in 9.2.4. Using Custom JAXBContext I have a custom ContextResolver class which is:
#Provider
#Produces({"application/xml"})
public class MyJaxbContextProvider implements ContextResolver<JAXBContext> {
private JAXBContext context;
public JAXBContext getContext(Class<?> type) {
if (context == null) {
try {
context = JAXBContext.newInstance("resttest.jaxb");
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
return context;
}
}
This ContextResolver is registered in the rest client with:
client = ClientBuilder.newClient().register(MoxyXmlFeature.class).register(MyJaxbContextProvider.class);
My Customer entity is :
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"id",
"firstname",
"lastname",
"street",
"city"
})
#XmlRootElement(name = "CUSTOMER")
public class Customer {
#XmlElement(name = "ID")
protected Integer id;
#XmlElement(name = "FIRSTNAME")
protected String firstName;
#XmlElement(name = "LASTNAME")
protected String lastName;
#XmlElement(name = "STREET")
protected String street;
#XmlElement(name = "CITY")
protected String city;
// getters and setters following
// ...
}
And finally the test class making the actual requests is:
public class RestClientTest {
private static Client client;
#BeforeClass
public static void beforeClass() {
client = ClientBuilder.newClient().register(MoxyXmlFeature.class).register(MyJaxbContextProvider.class);
}
#Test
public void testCreateCustomerWithEntity() { // Error
Customer customer = new Customer();
customer.setId(50);
customer.setFirstName("Nikol");
Response res = client.target("http://www.thomas-bayer.com/sqlrest/CUSTOMER/").request()
.post(entity(customer, MediaType.APPLICATION_XML_TYPE));
}
#Test
public void testGetCustomer() { // Error
Customer customer = client.target("http://www.thomas-bayer.com/sqlrest/CUSTOMER/3/").request()
.get(new GenericType<Customer>() {});
assertThat(customer.getId(), equalTo(3));
}
}
I have packed these files in a resttest project at https://github.com/georgeyanev/resttest
After cloning the tests can be executed simply with
mvn test
I expect when I'm making a POST requests and passing a Customer instance the latter to be marshalled by the jersey client (testCreateCustomerWithEntity).
And when I'm making a GET request the returned Customer entity to be unmarshalled (testGetCustomer).
But both tests fail with MessageBodyProviderNotFoundException saying that there is no MessageBodyWriter/MessageBodyReader found for media type application/xml and type Customer.
I'm using 2.19 version of both jersey-client and jersey-media-moxy libraries with oracle java 1.8.0_25
What could be the possible reason for this?
It appears that an additional dependency for jersey-media-jaxb is needed in order for the custom ContextResolver to be picked by jersey. Then the standard JAXB mechanisms are used to define the JAXBContextFactory from which a JAXBContext instance would be obtained.
In this case the JAXBContextFactory class is specified in jaxb.properties file in resttest.jaxb package.

how to get XmlElement of unmarshaled java object

#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Item", propOrder = {
"code",
"name",
"price"
})
#XmlRootElement(name="inventory")
public class Item {
#XmlElement(name="catalog_num", required = true)
protected String code;
#XmlElement(name="catalog_descrip", required = true)
protected String name;
#XmlElement(name="prod_price")
protected double price;
public String getCode() {
return code;
}
JAXBContext databaseJC = JAXBContext.newInstance(Item.class);
Unmarshaller databaseUnmarshaller = databaseJC.createUnmarshaller();
File databaseXML = new File("src/forum6838882/database.xml");
Item item = (Item) databaseUnmarshaller.unmarshal(databaseXML);
My question is:
How could I get the #XmlElement(name="catalog_num", required = true) from item object. I need know the name="catalog_num" here.
JAXB (JSR-222) does not provide an API to introspect the metadata. You can however use the Java Reflection APIs (java.lang.reflect) to get the annotations and examine them yourself.
Demo
import java.lang.reflect.*;
import javax.xml.bind.annotation.*;
public class Demo {
public static void main(String[] args) throws Exception {
Field field = Item.class.getDeclaredField("code");
XmlElement xmlElement = field.getAnnotation(XmlElement.class);
System.out.println(xmlElement.name());
}
}
Output
catalog_num

Categories

Resources