Line number of individual XML element while unmarshalling using jaxb - jaxb

I have a class Person with attributes name and address. I display it in a XML. While unmarshalling from XML will it be possible to get line number for name and address separately.
I tried using Locator. But it does not provide individual line numbers.

The EclipseLink JAXB (MOXy) and the JAXB reference implementation each have their own #XmlLocation annotations for supporting this use case. This allows you to store the location on the XML element corresponding to the object as an instance of org.xml.sax.Locator. Since I'm the MOXy lead, I will demonstrate using MOXy:
Person
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlLocation;
import org.xml.sax.Locator;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Person {
#XmlJavaTypeAdapter(value=StringAdapter.class)
String name;
Address address;
#XmlLocation
Locator locator;
}
Address
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlLocation;
import org.xml.sax.Locator;
public class Address {
#XmlJavaTypeAdapter(value=StringAdapter.class)
private String street;
#XmlLocation
Locator locator;
}
StringAdapter
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.eclipse.persistence.oxm.annotations.XmlLocation;
import org.xml.sax.Locator;
public class StringAdapter extends XmlAdapter<StringAdapter.AdaptedString, String> {
public static class AdaptedString {
#XmlValue
public String value;
#XmlLocation
#XmlTransient
Locator locator;
}
#Override
public String unmarshal(AdaptedString v) throws Exception {
System.out.println(v.value + " " + v.locator.getLineNumber());
return v.value;
}
#Override
public AdaptedString marshal(String v) throws Exception {
AdaptedString adaptedString = new AdaptedString();
adaptedString.value = v;
return adaptedString;
}
}
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14455596/input.xml");
Person person = (Person) unmarshaller.unmarshal(xml);
System.out.println("Person: " + person.locator.getLineNumber());
System.out.println("Address: " + person.address.locator.getLineNumber());
}
}
Output
Jane Doe 3
1 A Street 5
Person: 2
Address: 4

You could leverage a StAX StreamReaderDelegate and do something like the following:
Demo
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.stream.util.StreamReaderDelegate;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
StreamSource source = new StreamSource("src/forum14455596/input.xml");
XMLStreamReader xsr = xif.createXMLStreamReader(source);
xsr = new StreamReaderDelegate(xsr) {
#Override
public String getLocalName() {
String localName = super.getLocalName();
if(isStartElement()) {
System.out.println(localName + " " + this.getLocation().getLineNumber());
}
return localName;
}
};
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.unmarshal(xsr);
}
}
Person
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Person {
private String name;
private String address;
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>Jane Doe</name>
<address>1 A Street</address>
</person>
Output
person 2
name 3
address 4

Related

How to unmarshall JSON array using EclipseLink MOXy JAXB provider

This seems simple, but I must be missing something. All I am trying to do is unmarshall a JSON array. I have code that works when the JSON is a simple object, but when I make it an array (ie..surround JSON with []), it fails. Here is the sample json, domain class, unmarshalling code, and exception. Any help will be greatly appreciated.
Sample JSON:
[{"CreateIndex":24988,"ModifyIndex":132476,"LockIndex":0,"Key":"Redirector","Flags":0}
]
Domain:
package eclipselink.example.moxy.json.simple.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Optional;
import com.google.common.io.BaseEncoding;
import com.orbitz.consul.model.kv.ImmutableValue;
import com.orbitz.consul.util.UnsignedLongDeserializer;
#org.immutables.value.Value.Immutable
#JsonDeserialize(as = ImmutableValue.class)
#JsonSerialize(as = ImmutableValue.class)
#JsonIgnoreProperties(ignoreUnknown = true)
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class MyValue {
#XmlAttribute
private long createIndex;
#XmlAttribute
private long modifyIndex;
#XmlAttribute
private long lockIndex;
#XmlAttribute
private String key;
#XmlElement
private long flags;
#XmlElement(nillable = true)
private Optional<String> value;
#XmlElement
private Optional<String> session;
#JsonProperty("CreateIndex")
public long getCreateIndex() {
return createIndex;
}
#JsonProperty("ModifyIndex")
public long getModifyIndex() {
return modifyIndex;
}
#JsonProperty("LockIndex")
public long getLockIndex() {
return lockIndex;
}
#JsonProperty("Key")
public String getKey() {
return key;
}
#JsonProperty("Flags")
#JsonDeserialize(using=UnsignedLongDeserializer.class)
public long getFlags() {
return flags;
}
#JsonProperty("Value")
public Optional<String> getValue() {
return value;
}
#JsonProperty("Session")
public Optional<String> getSession() {
return session;
}
#JsonIgnore
#org.immutables.value.Value.Lazy
public Optional<String> getValueAsString() {
if (getValue() != null && getValue().isPresent()) {
return Optional.of(
new String(BaseEncoding.base64().decode(getValue().get()))
);
} else {
return Optional.absent();
}
}
}
Unmarshall code:
public class Main_JSON_redirector {
private static final String INPUT_XML = "META-INF/input.xml";
private static final String INPUT_JSON_URL = "http://192.168.85.186:8500/v1/kv/Redirector";
private static final File INPUT_JSON_FILE = new File("C:/Users/dnance/EclipseLink-examples/moxy/json-simple/src/main/resources/META-INF/redirector.json");
public static void main(String[] args) throws Exception {
System.out.println();
System.out.println("Running EclipseLink MOXy Simple MAIN_JSON Example");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);
JAXBContext jc = JAXBContext.newInstance(new Class[] {MyValue.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
StreamSource source = new StreamSource(INPUT_JSON_FILE);
JAXBElement<MyValue[]> jaxbElement = unmarshaller.unmarshal(source, MyValue[].class);
MyValue[] value = jaxbElement.getValue();
System.out.println("value: " + value[0].getValueAsString());
...
Exception:
Running EclipseLink MOXy Simple MAIN_JSON Example
Exception in thread "main" javax.xml.bind.UnmarshalException
- with linked exception:
[Exception [EclipseLink-25007] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: A descriptor for class [Leclipselink.example.moxy.json.simple.model.MyValue; was not found in the project. For JAXB, if the JAXBContext was bootstrapped using TypeMappingInfo[] you must call a marshal method that accepts TypeMappingInfo as an input parameter.]
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.handleXMLMarshalException(JAXBUnmarshaller.java:1072)
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:341)
at eclipselink.example.moxy.json.simple.Main_JSON_redirector.main(Main_JSON_redirector.java:58)
Caused by: Exception [EclipseLink-25007] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: A descriptor for class [Leclipselink.example.moxy.json.simple.model.MyValue; was not found in the project. For JAXB, if the JAXBContext was bootstrapped using TypeMappingInfo[] you must call a marshal method that accepts TypeMappingInfo as an input parameter.
at org.eclipse.persistence.exceptions.XMLMarshalException.descriptorNotFoundInProject(XMLMarshalException.java:154)
at org.eclipse.persistence.internal.oxm.Context$ContextState.getSession(Context.java:137)
at org.eclipse.persistence.oxm.XMLContext$XMLContextState.getSession(XMLContext.java:798)
at org.eclipse.persistence.oxm.XMLContext$XMLContextState.getSession(XMLContext.java:1)
at org.eclipse.persistence.internal.oxm.Context.getSession(Context.java:458)
at org.eclipse.persistence.oxm.XMLContext.getSession(XMLContext.java:366)
at org.eclipse.persistence.oxm.XMLContext.getSession(XMLContext.java:1)
at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:837)
at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:710)
at org.eclipse.persistence.internal.oxm.XMLUnmarshaller.unmarshal(XMLUnmarshaller.java:643)
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:339)
You just need to specify type class instead of array class in unmarshal method, E.g.
Collection<MyValue> myValues = (Collection<MyValue>) unmarshaller.unmarshal(source, MyValue.class).getValue();
Since you have unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false); it just works. Without it unmarshal returns JAXBElement<Collection<JAXBElement>>.

JAXB Binder.updateXml(objectTobeUpdated) fails when called twice

I have an existing XML, im using Jaxb to update it. Following code fails: there are bugs raised on this issue, but couldnt get any information about the fix. Can anyone kindly help on how to resolve the issue.
package la.te.st;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Student{
String name;
int age;
int id;
public String getName(){
return name;
}
#XmlElement
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
#XmlElement
public void setAge(int age){
this.age = age;
}
public int getId(){
return id;
}
#XmlAttribute
public void setId(int id){
this.id = id;
}
#Override
public String toString() {
return this.name + " age:" + this.age + " id:" + this.id;
}
}
/**************************************/
package la.te.st;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Binder;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class BinderDemo {
public static void main(String[] args) {
try {
// we need a blank document to store final xml output
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
Document document = docBuilder.parse("Student.xml");
// create JAXBContext which will be used to create a Binder
JAXBContext jc = JAXBContext.newInstance(Student.class);
Binder<Node> binder = jc.createBinder();
// set output as formatted one
binder.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// get xml node from the document
Node xmlNode = document.getDocumentElement();
// Returns the updated JAXB object
Student st = (Student)binder.updateJAXB(xmlNode);
System.out.println(st);
// set age and name
st.setAge(11);
st.setName("Sania");
System.out.println(st);
// update xml node with new data
xmlNode = binder.updateXML(st);
st.setAge(12);
st.setName("Sania");
System.out.println(st);
xmlNode = binder.updateXML(st);
// set node value to the document
document.setNodeValue(xmlNode.getNodeValue());
// finally print the edited object on stdout
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
t.transform(new DOMSource(document), new StreamResult(System.out));
}catch(Exception ex) {
ex.printStackTrace();
}
}
}
/*****************************/
the Student.xml file :
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<student id="10">
<age>10</age>
<name>Zara Ali</name>
</student>

JaxB XmlSeeAlso, XmlElementRef, newInstance with Classes

The answer to my question
JaxB reference resolving
led me to further pursue the details of the issue regarding the use of XmlSeeAlso, XmlElementReference and the specification of classes involved in JaxbContext.newInstance.
I started with trying to answer the question:
Is it possible to #XmlSeeAlso on a class without no-args constructor which has #XmlJavaTypeAdapter?
i created the Junit test below. To do so I came accross:
#XmlJavaTypeAdapter w/ Inheritance
#XmlSeeAlso alternative
In this state the code is compilable and runs - it marshals o.k. but does not umarshal as expected.
The marshal result is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Message>
<BasicInfo ref="id001"/>
</Message>
Unmarshalling does not create a BasicInfo as would be expected from the code in the Adapter:
public BasicInfo unmarshal(BasicInfoRef info) throws Exception {
BasicInfo binfo=new BasicInfo();
binfo.id=info.ref;
return binfo;
}
I find this all very confusing - things seem to be somewhat contradictory and mostly do not work as I'd expect regarding the JaxB settings involved. Most combinations do not work, those that do work do not give the full result yet. I assume that is also depending on the version and implementation being used.
What nees to be done to get this working?
What is a minimal set of XmlElementRef,XmlSeeAlso and JaxbContext newInstance referencing of the necessary classes?
How can I check which JaxB Implementation is being used? I am using EclipseLink 2.3.2? and I'm not sure whether this uses MoxY or not.
Update:
after consideration of:
JAXB xsi:type subclass unmarshalling not working
http://www.java.net/node/654579
http://jaxb.java.net/faq/#jaxb_version
http://www.eclipse.org/eclipselink/documentation/2.4/moxy/type_level003.htm
Can JAXB marshal by containment at first then marshal by #XmlIDREF for subsequent references?
The modified code below is close to what is intended.
Errors that showed up while trying were:
javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: unable to marshal type "com.bitplan.storage.jaxb.TestRefId$BasicInfo$BasicInfoRef" as an element because it is missing an #XmlRootElement annotation]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
The code has quite a few comments which can be commented in and out to find out what happens if one tries ...
I still have no clue what the optimal solution would be to avoid superfluous annotations.
modified code:
package com.bitplan.storage.jaxb;
import static org.junit.Assert.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.junit.Test;
/**
* Test the Reference/Id handling for Jaxb
*
* #author wf
*
*/
public class TestRefId {
#XmlDiscriminatorNode("#reforid")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapter(RefOrId.Adapter.class)
public static class RefOrId {
#XmlAttribute(name = "reforid")
public String reforid;
public RefOrId() {
}
#XmlAttribute
public String id;
#XmlAttribute
public String ref;
public static class Adapter extends XmlAdapter<RefOrId, RefOrId> {
#Override
public RefOrId marshal(RefOrId idref) throws Exception {
if (idref == null)
return null;
System.out.println("marshalling " + idref.getClass().getSimpleName()
+ " reforid:" + idref.reforid);
if (idref instanceof Ref)
return new Id(((Ref) idref).ref);
return idref;
}
#Override
public RefOrId unmarshal(RefOrId idref) throws Exception {
System.out.println("unmarshalling " + idref.getClass().getSimpleName()
+ " reforid:" + idref.reforid);
return idref;
}
}
}
#XmlDiscriminatorValue("id")
#XmlAccessorType(XmlAccessType.FIELD)
public static class Id extends RefOrId {
public Id() {
reforid = "id";
}
public Id(String pId) {
this();
this.id = pId;
}
}
#XmlDiscriminatorValue("ref")
#XmlAccessorType(XmlAccessType.FIELD)
public static class Ref extends RefOrId {
public Ref() {
reforid = "ref";
}
public Ref(String pRef) {
this();
this.ref = pRef;
}
}
/*
* https://stackoverflow.com/questions/8292427/is-it-possible-to-xmlseealso-on-a
* -class-without-no-args-constructor-which-has
*/
#XmlRootElement(name = "BasicInfo")
public static class BasicInfo {
public BasicInfo() {
};
public BasicInfo(String pId, String pInfo) {
this.id = new Id(pId);
this.basic = pInfo;
}
// #XmlTransient
public RefOrId id;
public String basic;
}
#XmlRootElement(name = "SpecificInfoA")
// #XmlJavaTypeAdapter(BasicInfo.Adapter.class)
public static class SpecificInfoA extends BasicInfo {
public SpecificInfoA() {
};
public SpecificInfoA(String pId, String pInfo, String pInfoA) {
super(pId, pInfo);
infoA = pInfoA;
}
public String infoA;
}
#XmlRootElement(name = "Message")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({ SpecificInfoA.class, BasicInfo.class })
public static class Message {
public Message() {
};
public Message(BasicInfo pBasicInfo) {
basicInfos.add(pBasicInfo);
}
// #XmlJavaTypeAdapter(BasicInfo.Adapter.class)
// #XmlElement(name="basicInfo",type=BasicInfoRef.class)
#XmlElementWrapper(name = "infos")
#XmlElement(name = "info")
public List<BasicInfo> basicInfos = new ArrayList<BasicInfo>();
}
/**
* marshal the given message
*
* #param jaxbContext
* #param message
* #return - the xml string
* #throws JAXBException
*/
public String marshalMessage(JAXBContext jaxbContext, Message message)
throws JAXBException {
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(message, sw);
String xml = sw.toString();
return xml;
}
/**
* test marshalling and umarshalling a message
*
* #param message
* #throws JAXBException
*/
public void testMessage(Message message) throws JAXBException {
#SuppressWarnings("rawtypes")
Class[] classes = { Message.class, SpecificInfoA.class, BasicInfo.class }; // BasicInfo.class,};
// https://stackoverflow.com/questions/8318231/xmlseealso-alternative/8318490#8318490
// https://stackoverflow.com/questions/11966714/xmljavatypeadapter-not-being-detected
JAXBContext jaxbContext = JAXBContext.newInstance(classes);
String xml = marshalMessage(jaxbContext, message);
System.out.println(xml);
Unmarshaller u = jaxbContext.createUnmarshaller();
Message result = (Message) u.unmarshal(new StringReader(xml));
assertNotNull(result);
assertNotNull(message.basicInfos);
for (BasicInfo binfo : message.basicInfos) {
RefOrId id = binfo.id;
assertNotNull(id);
System.out.println("basicInfo-id " + id.getClass().getSimpleName()
+ " for reforid " + id.reforid);
// assertEquals(message.basicInfo.id, result.basicInfo.id);
}
xml = marshalMessage(jaxbContext, result);
System.out.println(xml);
}
/**
* switch Moxy
*
* #param on
* #throws IOException
*/
public void switchMoxy(boolean on) throws IOException {
File moxySwitch = new File(
"src/test/java/com/bitplan/storage/jaxb/jaxb.properties");
if (on) {
PrintWriter pw = new PrintWriter(new FileWriter(moxySwitch));
pw.println("javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory");
pw.close();
} else {
moxySwitch.delete();
}
}
#Test
public void testStackOverflow8292427() throws JAXBException, IOException {
boolean[] moxyOnList = { false };
for (boolean moxyOn : moxyOnList) {
switchMoxy(moxyOn);
System.out.println("Moxy used: " + moxyOn);
Message message = new Message(new BasicInfo("basicId001",
"basicValue for basic Info"));
message.basicInfos.add(new SpecificInfoA("specificId002",
"basicValue for specific Info", "specific info"));
message.basicInfos.add(new SpecificInfoA("specificId002",
"basicValue for specific Info", "specific info"));
testMessage(message);
}
}
}
This is the Junit Test as mentioned above (before update):
package com.bitplan.storage.jaxb;
import static org.junit.Assert.*;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import org.junit.Test;
// Test the Reference/Id handling for Jaxb
public class TestRefId {
/*
* https://stackoverflow.com/questions/8292427/is-it-possible-to-xmlseealso-on-a-class-without-no-args-constructor-which-has
*/
#XmlRootElement(name="BasicInfo")
#XmlJavaTypeAdapter(BasicInfo.Adapter.class)
public static class BasicInfo {
#XmlRootElement(name="BasicInfo")
#XmlAccessorType(XmlAccessType.FIELD)
public static class BasicInfoRef {
#XmlAttribute(name = "ref")
public String ref;
}
public static class Adapter extends XmlAdapter<BasicInfoRef,BasicInfo>{
#Override
public BasicInfoRef marshal(BasicInfo info) throws Exception {
BasicInfoRef infoRef = new BasicInfoRef();
infoRef.ref=info.id;
return infoRef;
}
#Override
public BasicInfo unmarshal(BasicInfoRef info) throws Exception {
BasicInfo binfo=new BasicInfo();
binfo.id=info.ref;
return binfo;
}
} // Adapter
public String id;
public String basic;
}
#XmlJavaTypeAdapter(BasicInfo.Adapter.class)
public static class SpecificInfoA extends BasicInfo {
public String infoA;
}
#XmlRootElement(name="Message")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({SpecificInfoA.class,BasicInfo.class})
public static class Message {
// https://stackoverflow.com/questions/3107548/xmljavatypeadapter-w-inheritance
#XmlElementRefs({
#XmlElementRef(name="basic", type=BasicInfo.class),
// #XmlElementRef(name="infoA", type=SpecificInfoA.class)
})
public BasicInfo basicInfo;
}
#Test
public void testStackOverflow8292427() throws JAXBException {
Message message=new Message();
SpecificInfoA info=new SpecificInfoA();
info.id="id001";
info.basic="basicValue";
info.infoA="infoAValue";
message.basicInfo=info;
#SuppressWarnings("rawtypes")
Class[] classes= {Message.class,SpecificInfoA.class,BasicInfo.class,BasicInfo.BasicInfoRef.class}; // BasicInfo.class,};
// https://stackoverflow.com/questions/8318231/xmlseealso-alternative/8318490#8318490
JAXBContext jaxbContext = JAXBContext.newInstance(classes);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(message, sw);
String xml=sw.toString();
System.out.println(xml);
Unmarshaller u = jaxbContext.createUnmarshaller();
Message result = (Message) u.unmarshal(new StringReader(xml));
assertNotNull(result);
assertNotNull("basicInfo should not be null",result.basicInfo);
assertEquals("id001",result.basicInfo.id);
}
}

Hide a value in xml that can be retrieved only when unmarshalling

Is it possible to hide a value in XML such that it can be retrieved only when unmarshalling.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below are some options for implementing this use case if you are using MOXy as your JAXB provider. To use MOXy as your JAXB provider you need to include a file named jaxb.properties in the same package as your domain model with the following entry:
OPTION #1 - XmlAdapter
An XmlAdapter could be used to null out a property value during the marshal operation. While the XmlAdapter is a standard JAXB class, returning null from the marshal method causes an exception to occur when the JAXB reference implementation is used.
StringAdapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String string) throws Exception {
return null;
}
#Override
public String unmarshal(String string) throws Exception {
return string;
}
}
Person
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="Person")
public class Person {
String password;
#XmlJavaTypeAdapter(StringAdapter.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
OPTION #2 - Setter With No Getter
When MOXy is used as the JAXB provider if you have a property with an annotated setter and no getter then MOXy will treat it as a readonly property.
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Person")
public class Person {
String password;
#XmlElement
public void setPassword(String password) {
this.password = password;
}
}
OPTION #3 - MOXy's #XmlReadOnly Extension
MOXy's #XmlReadOnly extension can also be used to mark a property as read only.
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlReadOnly;
#XmlRootElement(name="Person")
public class Person {
String password;
#XmlReadOnly
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
DEMO CODE
input.xml
<Person>
<password> some password </password>
</Person>
*Demo*
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14231799/input.xml");
Person person = (Person) unmarshaller.unmarshal(xml);
System.out.println(person.password);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(person, System.out);
}
}
Output
some password
<?xml version="1.0" encoding="UTF-8"?>
<Person/>
The following approach could be used with any JAXB (JSR-222) implementation.
Demo
A Marshaller.Listener could be used to null out a value before the object is marshalled and then restore it afterwards.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14231799/input.xml");
Person person = (Person) unmarshaller.unmarshal(xml);
System.out.println(person.password);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setListener(new Marshaller.Listener() {
private String password;
#Override
public void afterMarshal(Object object) {
if(object instanceof Person) {
Person person = (Person) object;
person.setPassword(password);
password = null;
}
}
#Override
public void beforeMarshal(Object object) {
if(object instanceof Person) {
Person person = (Person) object;
password = person.getPassword();
person.setPassword(null);
}
}
});
marshaller.marshal(person, System.out);
System.out.println(person.password);
}
}
input.xml
<Person>
<password> some password </password>
</Person>
Output
some password
<?xml version="1.0" encoding="UTF-8"?>
<Person/>
some password

Use XmlIDRef for reference to abstract class

First of all a small example. The class ReferencingEntity holds a reference to the abstract class AbstractEntity. There are two implementations fo this class:
#XmlRootElement
public abstract class AbstractEntity {
#XmlID
private String id;
}
#XmlRootElement
public class EntityImpl1 extends AbstractEntity {
}
#XmlRootElement
public class EntityImpl2 extends AbstractEntity {
}
#XmlRootElement
public class ReferencingEntity {
#XmlIDREF
private AbstractEntity entity;
}
There is no problem marshalling an instance of ReferencingEntity (except that the concrete type is not present in xml), but when trying to unmarshal the xml representation, the descriptor is missing to determine the concrete implementation.
Currently I'm using an XmlAdapter to set all non-id fields null, but it would be better to use #XmlID if possible. Any ideas?
UPDATE:
I'm using RESTEasy in JBoss 6.1.0.Final and the provider creates the context as follows:
ContextResolver<JAXBContextFinder> resolver = providers.getContextResolver(JAXBContextFinder.class, mediaType);
JAXBContextFinder finder = resolver.getContext(type);
if (finder == null)
{
if (reader) throw new JAXBUnmarshalException("Could not find JAXBContextFinder for media type: " + mediaType);
else throw new JAXBMarshalException("Could not find JAXBContextFinder for media type: " + mediaType);
}
JAXBContext context = finder.findCachedContext(type, mediaType, annotations);
Below is my initial answer to your question. I imagine it will evolve as I better understand your use case.
ABOUT #XmlID/#XmlIDREF
Every instance referenced from a field/property annotated with #XmlIDREF also needs to be referenced via containment. I'll use the class below in this example.
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Root {
private AbstractEntity abstractEntity;
private ReferencingEntity referencingEntity;
public AbstractEntity getAbstractEntity() {
return abstractEntity;
}
public void setAbstractEntity(AbstractEntity abstractEntity) {
this.abstractEntity = abstractEntity;
}
public ReferencingEntity getReferencingEntity() {
return referencingEntity;
}
public void setReferencingEntity(ReferencingEntity referencingEntity) {
this.referencingEntity = referencingEntity;
}
}
REGARDING INHERITANCE
JAXB (JSR-222) implementations can't automatically discover subclasses, so you will need to be sure that the JAXBContext is aware of them. One way to accomplish this is to use the #XmlSeeAlso annotation on the parent class to point at the child classes.
import javax.xml.bind.annotation.*;
#XmlSeeAlso({EntityImpl1.class, EntityImpl2.class})
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class AbstractEntity {
#XmlID
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
DEMO CODE
Demo
package forum12111815;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum12111815/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
System.out.println(root.getAbstractEntity().getClass());
System.out.println(root.getAbstractEntity() == root.getReferencingEntity().getEntity());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<abstractEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="entityImpl2">
<id>123</id>
</abstractEntity>
<referencingEntity>
<entity>123</entity>
</referencingEntity>
</root>
Output
class forum12111815.EntityImpl2
true
<?xml version="1.0" encoding="UTF-8"?>
<root>
<abstractEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="entityImpl2">
<id>123</id>
</abstractEntity>
<referencingEntity>
<entity>123</entity>
</referencingEntity>
</root>
FOR MORE INFORMATION
http://blog.bdoughan.com/2010/10/jaxb-and-shared-references-xmlid-and.html
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html

Resources