Cannot read XML file using JAXB? - java

Having issues reading the following XML file that I create.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<prsettings>
<prsetting>
<players>
<player>
<lastDateEntered>0</lastDateEntered>
<lastTournament>1</lastTournament>
<playerId>0</playerId>
<playersStatus>unrated</playersStatus>
<playersTag>asfd</playersTag>
<score>5.0</score>
<setsPlayed>0</setsPlayed>
<tourneysWhileInactive>0</tourneysWhileInactive>
</player>
<player>
<lastDateEntered>0</lastDateEntered>
<lastTournament>1</lastTournament>
<playerId>1</playerId>
<playersStatus>unrated</playersStatus>
<playersTag>ba</playersTag>
<score>5.0</score>
<setsPlayed>0</setsPlayed>
<tourneysWhileInactive>0</tourneysWhileInactive>
</player>
<player>
<lastDateEntered>0</lastDateEntered>
<lastTournament>1</lastTournament>
<playerId>2</playerId>
<playersStatus>unrated</playersStatus>
<playersTag>asdgf</playersTag>
<score>5.0</score>
<setsPlayed>0</setsPlayed>
<tourneysWhileInactive>0</tourneysWhileInactive>
</player>
</players>
<challongeApiKey>asbg</challongeApiKey>
<challongeUsername>asf</challongeUsername>
<implementPointDecay>false</implementPointDecay>
<numSetsNeeded>5</numSetsNeeded>
<numTourneysForActive>2</numTourneysForActive>
<numTourneysForInnactive>5</numTourneysForInnactive>
<numTourneysNeeded>5</numTourneysNeeded>
<pointsRemoved>5</pointsRemoved>
<prName>asf</prName>
<removeInnactive>false</removeInnactive>
<showPlacingDiff>false</showPlacingDiff>
<showPointDiff>false</showPointDiff>
<startOfDecay>3</startOfDecay>
</prsetting>
I have an observableList of PRSetting objects and within the PRSetting objects I have an ArrayList of Players. This is why I created a POJO file and within the PRSetting Object the only object I set up was the following.
#XmlElementWrapper(name="players")
#XmlElement(name ="player")
private ArrayList<PlayerProfile> playersList = new ArrayList<PlayerProfile>();
Here is also my POJO file that is supposed to be used to write and read the XML file.
#XmlRootElement (name = "prsettings")
public class PRSettingsWrapper {
private ObservableList<PRSettings> prList;
#XmlElement(name = "prsetting")
public ObservableList<PRSettings> getPrList(){
return prList;
}
public void setPrList(ObservableList<PRSettings> prList){
this.prList = prList;
}
}
For some reason whenever I attempt to to load the data with the following code
JAXBContext context = JAXBContext
.newInstance(PRSettingsWrapper.class);
Unmarshaller um = context.createUnmarshaller();
// Reading XML from the file and unmarshalling.
PRSettingsData wrapper = (PRSettingsData) um.unmarshal(file);
prList.clear();
prList.addAll(wrapper.getPrList());
// Save the file path to the registry.
setPrSettingsFilePath(file);
I cannot seem to successfully load the xml files into the objects. The file path is working correctly, but I'm not sure what I'm doing wrong.
Thank you in advance for your help.

Sorry to mislead you about the cause.
The problem is caused by ObservableList;
You can refer to this article:
Marshalling ObservableList with JAXB
I modified code from the link above, and the followings should be workable.
anotherTest.MyContainer
package anotherTest;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "prsettings")
public class MyContainer extends MyObject {
private ObservableList<MyObject> children = FXCollections.observableArrayList();
#XmlElements({ #XmlElement(name = "prsetting", type = MyObject.class) })
public List<MyObject> getChildren() {
return children;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("children:");
for (MyObject node : children) {
sb.append("\n");
sb.append(" " + node.toString());
}
return sb.toString();
}
}
anotherTest.MyObject
package anotherTest;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlType;
#XmlType(name = "Object")
public class MyObject {
private String challongeApiKey;
#XmlElementWrapper(name = "players")
private List<PlayerProfile> player;
public String getChallongeApiKey() {
return challongeApiKey;
}
public void setChallongeApiKey(String challongeApiKey) {
this.challongeApiKey = challongeApiKey;
}
public List<PlayerProfile> getPlayer() {
return player;
}
public void setPlayers(List<PlayerProfile> player) {
this.player = player;
}
}
anotherTest.PlayerProfile
package anotherTest;
public class PlayerProfile {
private int playerId;
private String playersStatus;
public int getPlayerId() {
return playerId;
}
public void setPlayerId(int playerId) {
this.playerId = playerId;
}
public String getPlayersStatus() {
return playersStatus;
}
public void setPlayersStatus(String playersStatus) {
this.playersStatus = playersStatus;
}
}
anotherTest.Example
package anotherTest;
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.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Example {
public static void main(String[] args) {
// create container with list
MyContainer container = new MyContainer();
// add objects
MyObject object;
object = new MyObject();
object.setChallongeApiKey("ABCABC");
container.getChildren().add(object);
PlayerProfile p = new PlayerProfile();
p.setPlayerId(1);
p.setPlayersStatus("unrated");
List<PlayerProfile> l = new ArrayList<>();
l.add(0, p);
object.setPlayers(l);
// marshal
String baseXml = marshal(container);
// unmarshal
container = unmarshal(baseXml);
System.out.println("unmarshal test: " + container.getChildren().get(0).getChallongeApiKey());
System.out.println("unmarshal test: " + container.getChildren().get(0).getPlayer().get(0).getPlayerId());
System.out.println("unmarshal test: " + container.getChildren().get(0).getPlayer().get(0).getPlayersStatus());
System.exit(0);
}
public static String marshal(MyContainer base) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(MyContainer.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter stringWriter = new StringWriter();
jaxbMarshaller.marshal(base, stringWriter);
String xml = stringWriter.toString();
System.out.println("XML:\n" + xml);
return xml;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static MyContainer unmarshal(String xml) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(MyContainer.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
StringReader stringReader = new StringReader(xml);
MyContainer container = (MyContainer) jaxbUnmarshaller.unmarshal(stringReader);
return container;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
the output in the console is
XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<prsettings>
<prsetting>
<players>
<player>
<playerId>1</playerId>
<playersStatus>unrated</playersStatus>
</player>
</players>
<challongeApiKey>ABCABC</challongeApiKey>
</prsetting>
</prsettings>
unmarshal test: ABCABC
unmarshal test: 1
unmarshal test: unrated
Sorry, I don't know why either. I tested that if declare children to List<MyObject>, it can work properly. However, when it comes to ObservableList, you must declare it like this, or NullPointerException will occur when unmarshalling (but the same code works well on marshalling).
private ObservableList<MyObject> children = FXCollections.observableArrayList();

Related

Cannot construct java.util.Collection xstream

I am having troubles unmarshalling some xml using the XStream library. The related java class uses the java.util.Collection class in order to store some attributes, which I understand is a problem for XStream. However, I am unable to change the Java class to use something like ArrayList due to various reasons. Is there a way to unmarshal the xml using XStream, or should I search other libraries for a solution?
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import org.testng.annotations.Test;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ControllerTest {
#XStreamAlias("controllers")
public class ControllerList implements Serializable {
#XStreamImplicit(
itemFieldName = "controller"
)
private List<Controller> controllers = new ArrayList();
public ControllerList() {
}
public List<Controller> getControllers() {
return this.controllers;
}
public void setControllers(List<Controller> controllers) {
this.controllers = controllers;
}
}
#XStreamAlias("controller")
public class Controller extends BasicInfo {
#XStreamImplicit(
itemFieldName = "storageInfo"
)
private Collection<BasicInfo> storage;
public Controller() {
}
public Collection<BasicInfo> getStorage() {
return this.storage;
}
public void setStorage(Collection<BasicInfo> storage) {
this.storage = storage;
}
}
#XStreamAlias("basicinfo")
public class BasicInfo{
private String name;
public BasicInfo() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
#Test(groups = {"edge"})
public void testControllers() {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><controllers><controller><storageInfo>" +
"<name>My name</name></storageInfo></controller></controllers>";
XStream stream = new XStream();
stream.processAnnotations(ControllerList.class);
InputStream in = new ByteArrayInputStream(xml.getBytes());
try {
InputStreamReader rdr = new InputStreamReader(in, "UTF-8");
ControllerList controllers = (ControllerList) stream.fromXML(rdr);
} catch (UnsupportedEncodingException e) {
}
}
}
XStream CollectionConverter does not supports java.util.Collection. So, you can try in two ways:
replace Collection by List:
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
#XStreamAlias("controller")
public class Controller {
#XStreamImplicit(itemFieldName = "storageInfo")
private List<BasicInfo> storage;
public List<BasicInfo> getStorage() {
return storage;
}
public void setStorage(final List<BasicInfo> storage) {
this.storage = storage;
}
}
This test should work for the first case:
#Test
public void testControllers() {
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><controllers><controller><storageInfo><name>My name</name></storageInfo></controller></controllers>";
final XStream stream = new XStream();
stream.processAnnotations(ControllerList.class);
final ControllerList controllers = (ControllerList) stream.fromXML(xml);
final List<Controller> colls = controllers.getControllers();
Assert.assertEquals(colls.size(), 1);
final Controller coll = colls.get(0);
final List<BasicInfo> infos = coll.getStorage();
Assert.assertEquals(infos.size(), 1);
final BasicInfo info = infos.get(0);
Assert.assertEquals(info.getName(), "My name");
}
Add a default implementation to java.util.Collection. This test should work:
#Test
public void testControllers() {
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><controllers><controller><storageInfo><name>My name</name></storageInfo></controller></controllers>";
final XStream stream = new XStream();
stream.processAnnotations(ControllerList.class);
stream.addDefaultImplementation(ArrayList.class, Collection.class);
final ControllerList controllers = (ControllerList) stream.fromXML(xml);
final List<Controller> colls = controllers.getControllers();
Assert.assertEquals(colls.size(), 1);
final Controller coll = colls.get(0);
final Collection<BasicInfo> infos = coll.getStorage();
Assert.assertEquals(infos.size(), 1);
for (final BasicInfo info : infos) {
Assert.assertEquals(info.getName(), "My name");
}
}

JAXB. Get boolean from string

In my XML I have
<myelem required="false"/>
How I can read the required attribute as a boolean? I can read it as String and inside a getter do this: return new Boolean(required)
But maybe there are some more elegant ways?
Just simply use boolean for the member in your Java class:
#XmlAttribute
private boolean required;
Or, if you use getter-setter style of mapping:
#XmlAttribute
public boolean isRequired() {
return required;
}
The JAXB unmarshaller is able to interpret "true" and "false" strings in the XML document as boolean value.
UPDATE:
I tested this with the following classes:
test/MyElem.java:
package test;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="myelem")
public class MyElem {
private boolean required;
#XmlAttribute
public boolean isRequired() {
return required;
}
public void setRequired(boolean value) {
required = value;
}
}
Test.java:
import javax.xml.bind.*;
import java.io.*;
import test.*;
public class Test {
public static void main(String[] args) {
try {
JAXBContext jc = JAXBContext.newInstance(MyElem.class);
Unmarshaller u = jc.createUnmarshaller();
Object o = u.unmarshal( new File( "test.xml" ) );
System.out.println(((MyElem)o).isRequired());
} catch(Exception e) {
e.printStackTrace();
}
}
}
And with the following input (test.xml):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myelem required="true"/>
I get the correct result on the console:
true

JAXB Java generating XML, Why lowercase?

When I run this code:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
public class JavaToXMLDemo {
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(Employee.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Employee object = new Employee();
object.setCode("CA");
object.setName("Cath");
object.setSalary(300);
m.marshal(object, System.out);
}
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
class Employee {
private String code;
private String name;
private int salary;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int population) {
this.salary = population;
}
}
I get
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
<code>CA</code>
<name>Cath</name>
<salary>300</salary>
</employee>
Which is correct, so my question is why does it change the Employee to employee?
Is it possible to make it print with uppercase E, instead of employee?
This is what I actually wanted to have:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Employee>
<code>CA</code>
<name>Cath</name>
<salary>300</salary>
</Employee>
Thanks!
The behaviour you are seeing is the result of the standard JAXB (JSR-222) XML name to Java name conversion algorithm.
You can use the #XmlRootElement annotation to specify a name:
#XmlRootElement(name="Employee")
#XmlAccessorType(XmlAccessType.FIELD)
class Employee {
...
}
I'm the EclipseLink JAXB (MOXy) lead, and we have an extension that allows you to override the default name conversion algorithm that you may be interested in:
http://blog.bdoughan.com/2011/05/overriding-jaxbs-name-mangling.html
For specific elements...
#XmlElement( name = "Code")
private String code;
For the object....
#XmlRootElement(name="Employee")
public class Employee{ ...
My solution after put #XmlElement(name="Xxxxx") to fields and used XStream.aliasField(). This is more generic because it uses annotations and scans other class calls in the same package.
import java.lang.reflect.Field;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlAttribute;
import com.thoughtworks.xstream.XStream;
import my.MyClassGeneratedFromXsdToJaxB;
public class TestChangeFirstLetterXml {
public static void main(String[] args) throws ClassNotFoundException {
MyClassGeneratedFromXsdToJaxB myClassGeneratedFromXsdToJaxB=new MyClassGeneratedFromXsdToJaxB();
XStream xstream = new XStream();
xstream.autodetectAnnotations(true);
xstream = makeAliasAnnotatedFields(xstream, MyClassGeneratedFromXsdToJaxB.class, "FirstTagOrRoot");
//System.out.println(xstream.toXML(myClassGeneratedFromXsdToJaxB));
}
public static XStream makeAliasAnnotatedFields(XStream xstream, Class myclass, String firstTag)
throws ClassNotFoundException {
xstream.alias(firstTag, myclass);
Map<String, Object[]> aliaslist = getListAlias(myclass);
for (String key : aliaslist.keySet()) {
Object[] aliasvalue = new Object[3];
aliasvalue = aliaslist.get(key);
String xmlTag = new String((String) aliasvalue[0]);
Class<?> classJaxb = (Class<?>) aliasvalue[1];
String tagToRename = new String((String) aliasvalue[2]);
xstream.aliasField(xmlTag, classJaxb, tagToRename);
System.out.println("AliasField " + xmlTag + " " + classJaxb.getName() + " " + tagToRename);
}
return xstream;
}
public static Map<String, Object[]> getListAlias(Class<?> classToCheck)
throws ClassNotFoundException {
/* Read recursive fields of the class */
Field[] fs = classToCheck.getDeclaredFields();
String annotationsPackage = classToCheck.getPackage().getName();
String classSimpleName = new String(classToCheck.getSimpleName());
/* it is necessary avoid loop */
Map<String, Object[]> aliasStart = new TreeMap<String, Object[]>();
/* */
for (int i = 0; i < fs.length; i++) {
String nameField = fs[i].getName();
String classFieldName = new String(fs[i].getType().getName());
String nameXmlXsd = new String("");
String idkey = new String(annotationsPackage + ".");
if (fs[i].isAnnotationPresent(javax.xml.bind.annotation.XmlElement.class)) {
XmlElement atrib = fs[i].getAnnotation(XmlElement.class);
nameXmlXsd = new String(atrib.name());
idkey = new String(idkey + classSimpleName + ".Element." + nameField);
} else if (fs[i].isAnnotationPresent(javax.xml.bind.annotation.XmlAttribute.class)) {
XmlAttribute atrib = fs[i].getAnnotation(XmlAttribute.class);
nameXmlXsd = new String(atrib.name());
idkey = new String(idkey + classSimpleName + ".Type." + nameField);
}
if (aliasStart.containsKey(idkey)) /* avoid loop */
continue;
if (nameXmlXsd.equals("Signature")) // My particular condition
continue;
if (!nameXmlXsd.equals(classFieldName)) {
// xstrem.aliasField(a,b,c)
Object[] alias = new Object[3];
alias[0] = new String(nameXmlXsd);
alias[1] = classToCheck;
alias[2] = new String(nameField);
aliasStart.put(idkey, alias);
}
if (classFieldName.indexOf(annotationsPackage) > -1) {
Class<?> c = Class.forName(classFieldName);
Map<String, Object[]> aliaslist = getListAlias(c);
for (String key : aliaslist.keySet()) {
Object[] aliasvalue = new Object[3];
aliasvalue = aliaslist.get(key);
aliasStart.put(key, aliasvalue);
}
}
}
return aliasStart;
}
}
An alternative answer, if JAXB is not a MUST, then you can actually use org.json jar to convert the object to a JSONObject, then from there, you can use the XML object to convert the JSONObject to an XML. You will need a few tweaks before it can be a standalone XML.
A code snippet example:
public static String getXMLString(Object o){
JSONObject json = new JSONObject(o);
String result =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
XML.toString(json, o.getClass().getSimpleName());
return result;
}

xml:base in JAXB

I have some XML like this:
<root xml:base="http://www.example.com/foo">
<childElement someAttribute="bar/blort.html"/>
<childElement someAttribute="bar/baz/foo.html"/>
</root>
The schema for my XML defines someAttribute as being of type xs:anyURI
I want to use JAXB to unmarshall the XML into an object model a bit like this:
#XmlRootElement(name="root")
class Root {
#XmlElement(name="childElement")
private List<Child> _children;
}
class Child {
#XmlAttribute(name="someAttribute")
private URI _someAttribute;
}
I would like values of someAttribute to be resolved according to XML base, i.e. when I unmarshall the XML given above, I want the childrens' attributes to be resolved to java.net.URI instances with values http://www.example.com/foo/bar/blort.html and so on.
I was hoping a custom XmlAdapter would allow me to achieve the right result, but the XmlAdapter has no access to the surrounding context, in particular, the value of xml:base in effect at that point (note that this is not as simple as the most recent enclosing value of xml:base as xml:base can appear anywhere in the tree, and relative xml:bases must be resolved against their ancestors).
I'm using EclipseLink's MOXY implementation of JAXB, if it matters.
You can leverage an XMLStreamReader and an XmlAdapter to implement this use case:
UriAdapter
The UriAdapter is both an XmlAdapter for handling the URI property, and a StreamFilter that we will use to detect the xml:base attribute.
package forum9906642;
import java.net.URI;
import javax.xml.bind.annotation.adapters.*;
import javax.xml.stream.*;
public class UriAdapter extends XmlAdapter<String, URI> implements StreamFilter {
private String base = "";
public UriAdapter() {
}
public UriAdapter(String base) {
this.base = base;
}
public URI unmarshal(String string) throws Exception {
return new URI(base + '/' + string);
}
public String marshal(URI uri) throws Exception {
if("".equals(base)) {
return uri.toString();
} else {
URI baseURI = new URI(base);
return baseURI.relativize(uri).toString();
}
}
public boolean accept(XMLStreamReader reader) {
if(reader.isStartElement()) {
String newBase = reader.getAttributeValue("http://www.w3.org/XML/1998/namespace", "base");
if(null != newBase) {
base = newBase;
}
}
return true;
}
}
Demo
The code below demonstrates how to use all the pieces together:
package forum9906642;
import java.io.*;
import javax.xml.bind.*;
import javax.xml.stream.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
UriAdapter uriAdapter = new UriAdapter();
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("src/forum9906642/input.xml"));
xsr = xif.createFilteredReader(xsr, uriAdapter);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setAdapter(uriAdapter);
Root root = (Root) unmarshaller.unmarshal(xsr);
for(Child child : root.getChildren()) {
System.out.println(child.getSomeAttribute().toString());
}
Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(uriAdapter);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Child
package forum9906642;
import java.net.URI;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
#XmlAccessorType(XmlAccessType.FIELD)
class Child {
#XmlAttribute(name="someAttribute")
#XmlJavaTypeAdapter(UriAdapter.class)
private URI _someAttribute;
public URI getSomeAttribute() {
return _someAttribute;
}
public void setSomeAttribute(URI _someAttribute) {
this._someAttribute = _someAttribute;
}
}
Output
http://www.example.com/foo/bar/blort.html
http://www.example.com/foo/bar/baz/foo.html
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<childElement someAttribute="bar/blort.html"/>
<childElement someAttribute="bar/baz/foo.html"/>
</root>
I had a similar problem, but with nested xml:base (because of XInclude), so I ended up having to do this:
public class URIFixingUnmarshaller {
private final JAXBContext jaxb;
public URIFixingUnmarshaller(JAXBContext jaxb) {
this.jaxb = jaxb;
}
public <T> JAXBElement<T> unmarshal(SAXSource in, Class<T> as)
throws JAXBException {
CurrLocation curr = new CurrLocation(in.getXMLReader());
Unmarshaller u = jaxb.createUnmarshaller();
u.setListener(new URIUpdater(curr));
return u.unmarshal(new SAXSource(curr, in.getInputSource()), as);
}
private static class CurrLocation extends XMLFilterImpl {
private Locator curr;
public CurrLocation(XMLReader actual) {
setParent(actual);
}
#Override
public void setDocumentLocator(Locator to) {
super.setDocumentLocator(to);
this.curr = to;
}
String resolve(String uri) {
try {
URL base = new URL(curr.getSystemId());
URL absolute = new URL(base, uri);
return absolute.toString();
} catch (MalformedURLException probablyAlreadyAbsolute) {
return uri;
}
}
}
private static class URIUpdater extends Unmarshaller.Listener {
private final CurrLocation curr;
URIUpdater(CurrLocation curr) {
this.curr = curr;
}
#Override
public void afterUnmarshal(Object target, Object parent) {
if (target instanceof SomethingWithRelativeURI) {
SomethingWithRelativeURI casted = (SomethingWithRelativeURI) target;
casted.setPath(curr.resolve(casted.getPath()));
}
}
}
}

JAXB + Enums + Showing Multiple Values

I have an enum:
#XmlEnum
#XmlRootElement
public enum Product {
POKER("favourite-product-poker"),
SPORTSBOOK("favourite-product-casino"),
CASINO("favourite-product-sportsbook"),
SKILL_GAMES("favourite-product-skill-games");
private static final String COULD_NOT_FIND_PRODUCT = "Could not find product: ";
private String key;
private Product(final String key) {
this.key = key;
}
/**
* #return the key
*/
public String getKey() {
return key;
}
that I output in a REST service like so:
GenericEntity<List<Product>> genericEntity = new GenericEntity<List<Product>>(products) {
};
return Response.ok().entity(genericEntity).build();
and it outputs like this:
<products>
<product>POKER</product>
<product>SPORTSBOOK</product>
<product>CASINO</product>
<product>SKILL_GAMES</product>
</products>
I want it to output with both the enum name (i.e, POKER) and the key (i.e, "favourite-product-poker").
I have tried a number of different ways of doing this including using #XmlElement, #XmlEnumValue and #XmlJavaTypeAdapter, without getting both out at the same time.
Does anyone know how to achieve this, as you would for a normal JAXB annotated bean?
Thanks.
You could create a wrapper object for this, something like:
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
#XmlRootElement(name="product")
public class ProductWrapper {
private Product product;
#XmlValue
public Product getValue() {
return product;
}
public void setValue(Product value) {
this.product = value;
}
#XmlAttribute
public String getKey() {
return product.getKey();
}
}
This would correspond to the following XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product key="favourite-product-poker">POKER</product>
You would need to pass instances of ProductWrapper to JAXB instead of Product.
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ProductWrapper.class);
ProductWrapper pw = new ProductWrapper();
pw.setValue(Product.POKER);
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(pw, System.out);
}
}
You can use an adapter:
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
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.XmlType;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
public class XmlEnumTest{
public static void main(String...str) throws Exception{
JAXBContext jc = JAXBContext.newInstance(ProductList.class);
StringWriter sw = new StringWriter();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(new ProductList(),sw);
System.out.println(sw.toString());
}
}
class ProductTypeAdaper extends XmlAdapter<ProductAdapter, Product> {
#Override
public Product unmarshal(ProductAdapter v) throws Exception {
return Product.valueOf(v.value);
}
#Override
public ProductAdapter marshal(Product v) throws Exception {
ProductAdapter result = new ProductAdapter();
result.key = v.getKey();
result.value = v.name();
return result;
}
}
#XmlType
class ProductAdapter{
#XmlAttribute
public String key;
#XmlValue
public String value;
}
#XmlJavaTypeAdapter(ProductTypeAdaper.class)
enum Product{
POKER("favourite-product-poker"),
SPORTSBOOK("favourite-product-casino"),
CASINO("favourite-product-sportsbook"),
SKILL_GAMES("favourite-product-skill-games");
private static final String COULD_NOT_FIND_PRODUCT = "Could not find product: ";
private String key;
private Product(final String key) {
this.key = key;
}
/**
* #return the key
*/
public String getKey() {
return key;
}
}
#XmlRootElement
#XmlSeeAlso({Product.class})
class ProductList{
#XmlElementWrapper(name="products")
#XmlElement(name="product")
private List<Product> list = new ArrayList<Product>(){{add(Product.POKER);add(Product.SPORTSBOOK);add(Product.CASINO);}};
}
You need to remove the #XmlEnum from your enum value, if you want it to be serialized into XML like a normal object. An enum (by definition) is represented in the XML by a single string symbol. This allows combining it with #XmlList, for example, to create an efficient, whitespace-separated list of items.

Categories

Resources