JAXB unmarshalling based on element's value - java

I would like to unmarshall an object using JAXB based on the enum value/string present in the xml. I have several classes inheriting from one abstract class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({ InheritingClassOne.class, InheritingClassTwo.class })
public abstract class BasicClass {
protected String type; //determines class type
protected String description;
//getters & setters
}
two examplary subclasses:
#XmlRootElement(name = "someRoot")
#XmlAccessorType(XmlAccessType.FIELD)
public class InheritingClassOne extends BasicClass {
private String message;
private Float mark;
//getters & setters
}
#XmlRootElement(name = "someRoot")
#XmlAccessorType(XmlAccessType.FIELD)
public class InheritingClassTwo extends BasicClass {
private Integer value;
//getters & setters
}
I know that JAXB can unmarshall objects based on xsi:type attribute, which I cannot use, since the input xml is stripped from all atributes. I have tried using Moxy #XmlDiscriminatorNode & #XmlDiscriminatorValue, but these seem to work only with attributes and not the element values.
I have also seen #XmlElementRef which lets determine the type based on the name of the element, but again, due to some restrictions all elements have to have the same name in input and output xml.
My input xml is:
<someRoot>
<type>chooseOne</type>
<message>Message for InheritingClassOne</message>
<mark>12.3</mark>
</someRoot>
I did not find solution for this problem other than using #XmlJavaAdapter with defined adapter:
public class CustomAdapter extends XmlAdapter<Object, BasicClass> {
#Override
public BasicClass unmarshal(Object v) throws Exception {
//TODO: cast v to Element interface, get to type element value and handle accordingly
return null;
}
#Override
public Object marshal(BasicClass v) throws Exception {
//TODO: marshal
return null;
}
}
The adapter solution with reading ElementNsImpl child values to get the category seems awful and takes a lot of effort for such task. Are there any solutions that I am missing? Can I somehow change my models (without using xml attributes), so this task is doable?

Related

Is it possible for Jaxb to unmarshall to an interface?

my current code marshalls perfectly, and I get the element I want inside of my resulting XML. i.e. <food>Beef</food>
However, the problem comes when I have to unmarshall this back to a java object. Everything returns fine except the food variable. I originally did not have the XmlElement(required = true) on top, and the food element would always unmarshal back to null. Then, I added the required=true section and I am getting issues with the interface. I did some digging and from what I can gather, jaxb can't really unmarshal into an interface since it doesn't know the concrete type to marshall into.
Current error if this helps:
Can not set FoodInterface field BigPayload.food to
com.sun.org.apache.xerces.internal.dom.ElementNSImpl
My Java classes are as follows:
#XmlSeeAlso({MeatFoods.class, VeggieFoods.class})
#XmlType(name ="BigPayload", propOrder = //stuff goes here
#XmlRootElement(name = foodPayload)
public class BigPayload implements Payload{
#XmlElements({#XmlElement(type = MeatFoods.class),
#XmlElement(type = VeggieFoods.class),
#XmlElement(required = true)})
protected FoodInterface food;
protected Grade grade;
//grade/food setters and getters
}
#XmlTransient //If this isn't here, I get the jaxB cannot handle interfaces and no default constructor error
public interface FoodInterface{ //stuff here}
#XmlType(name = "MeatFoods")
#XmlEnum
public enum MeatFoods implements FoodInterface{
Chicken(1, Chicken)
Beef(2, Beef)
Pork(3, Pork)
int value;
String name;
#Override
public int getValue()
#Override
public String getName()
public static FoodInterface getEnumFromValue(int value){//gets stuff}
public static FoodInterface getEnumFromName(String name){//gets stuff}
}
I just wanted to know if that is correct, and there's no real good way to unmarshall an interface type. Is this true? I saw a lot of other questions were about marshalling interfaces, and the unmarshalling questions did not really get answers to my satisfaction. Any answer is appreciated, and I know this isn't a minimal reproducible example, but I'm more looking for a verbal answer instead of a code fix or anything. Although, if there's anything blatantly wrong in the code please let me know!
For the standard cases JAXB can only use (abstract) classes not interfaces.
Options that i can think of
You can use interfaces with #XmlAdapter. See example: [1]
Use Object for JAXB Bindings and expose the interface with casting. (Maybe add validation logic into the `afterUnmarshal(Unmarshaller u, Object parent). [2]
Bind a private field to #XmlAnyElement and do some further processing in afterUnmarshal(Unmarshaller, Object), add #XmlTransient to the target. See example: [3]
With some creativity there might be some other options. But i think all boil down to bascially: try to get to the "raw" parsing options and fill the interface reference manually.
[1]
public static interface Food {
String name();
}
public enum Veggie implements Food {
SALAD;
}
public static enum Meat implements Food {
CHICKEN;
}
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement
public static class UseInterface {
#XmlJavaTypeAdapter(FoodAdapter.class)
#XmlAttribute
private Food food;
public Food getFood() {
return food;
}
public void setFood(Food food) {
this.food = food;
}
}
public static class FoodAdapter extends XmlAdapter<String, Food> {
#Override
public Food unmarshal(String v) throws Exception {
try {
return Veggie.valueOf(v);
} catch (IllegalArgumentException e) {
}
try {
return Meat.valueOf(v);
} catch (IllegalArgumentException e) {
}
throw new IllegalArgumentException("Unknown Food:" + v);
}
#Override
public String marshal(Food v) throws Exception {
return v.name();
}
}
[2]
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement
public static class UseInterface {
#XmlElement
private Object food;
public Food getFood() {
return (Food) food;
}
public void setFood(Food food) {
this.food = food;
}
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
if (food != null && !(food instanceof Food)) {
throw new IllegalArgumentException("food is of wrong type: " + food.getClass().getName());
}
}
}
JAXBContext newInstance = JAXBContext.newInstance(UseInterface.class, Meat.class, Veggie.class);
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><useInterface><food xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"meat\">CHICKEN</food></useInterface>";
newInstance.createUnmarshaller().unmarshal(new StringReader(xml));
[3]
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement
public static class UseInterface {
#XmlAnyElement
private org.w3c.dom.Element foo;
#XmlTransient
private SomeInterface ifc
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
NamedNodeMap attributes = foo.getAttributes();
// do something with foo on DOM level to bind the subtree to an interface manually
}
}

Java Gson ClassCastException

I am building a Java Servlets 3.0 REST API and using Gson to serialize some data to json.
I get this error, though:
java.lang.ClassCastException: za.co.a.models.tables.sybase.Family cannot be cast to java.util.Map
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:145)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:97)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
at com.google.gson.Gson.toJson(Gson.java:704)
at com.google.gson.Gson.toJson(Gson.java:683)
at com.google.gson.Gson.toJson(Gson.java:638)
at com.google.gson.Gson.toJson(Gson.java:618)
at za.co.a.helpers.JsonHelper.toJson(JsonHelper.java:33)
at za.co.a.models.tables.sybase.ActiveProcesses.saveProcess(ActiveProcesses.java:57)
My code is as follows:
#Table(name = "E_FAMILY")
public class Family extends IGenericModel <Family>
{
#Id
public BigDecimal EMPLOYEE_ID;
#Id
public BigDecimal FAMILY_ID;
#Id
public BigDecimal COMPANY_ID;
public String FIRSTNAME;
public String SECONDNAME;
public String SURNAME;
public String RELATION;
public int RELATION_ID;
public String MED_DEPENDENT_YN;
public String TAX_DEPENDENT_YN;
public String GENDER;
public Date BIRTHDATE;
public String TEL_HOME;
public String TEL_WORK;
public String TEL_CELL;
public String E_MAIL;
...
}
The calling code:
public String toJson(Object obj)
{
return gson.toJson(obj);
}
Family, in this case is field in a larger class, however, I'm not having any problems with any other fields or any other classes that are similar. This is the first time, in the year I've been developing this, that this error comes up. Is there a limit to the size or complexity of class Gson can serialize? Or what can I check to see what's causing the error? Or is there a way change this specific mapping, (though I don't understand why Google is trying to map this class to Map)?
Thanks
Sethmo
Edit Including class hierachy
IGenericModel and IGenericReadOnlyModel only contain functions. IModel has 2 members, but I've added it as part of an ExclusionStrategy so that those members don't get serialized.
public class IGenericModel<T> extends IGenericReadOnlyModel
{
}
public class IGenericReadOnlyModel<T> extends IModel
{
}
public class IModel
{
protected String dbEngine;
protected IDatabase db;
}
Edit rest of the code
Ok, the class that holds Family is quite large and mostly full of Strings, Dates and Booleans and ints. Where Family comes in is here, the two objects are passed from the front-end and represent the old and new values (users can edit, add and delete family members in the UI, then submit those lists).
public abstract class IWebProcess extends IModel
{
protected Object _OldValue;
protected Object _NewValue;
}
Once submitted (as JSON from the UI), it's serialized:
Type familyType = new TypeToken<LinkedList<Family>>(){}.getType();
LinkedList<Family> oldFamily = gson.fromJson(oldFamilyJson, familyType);
LinkedList<Family> newFamily = gson.fromJson(newFamilyJson, familyType);
Then, the concrete class is then initialized:
IWebProcess family = WebProcess_FamilyRequisition(oldFamily,newFamily,...,...,...)
then, in the constructor of WebProcess_FamilyRequisition, I call super(oldFamily, newFamily) and then in the constructor of IWebProcess:
this._OldValue = oldFamily
this._NewValue = newFamily
I do all this casting because I save the new values to the DB first, before serializing the entire WebProcess to the DB. I've made _OldValue and _NewValue Objects because this is a base class for 8 other classes that work the same and they serialize just fine.

Complex POJO mapping using Dozer

I am new to Dozer, and have done flat mapping from one POJO to another using Dozer xml mapping.But now I want to map complex POJO which is given below and I am stucked how to do it.
// -----------------------Source Classes-----------------------------
public class Source{
public String sourceId;
public Product product;
public List<Item> items;
}
public class Product{
public Integer productId;
public String productName;
}
public class Item{
public Integer id;
public Integer qty;
public String desc;
}
// -----------------------Destination Classes-------------------
public class Destination{
public String destId;
public DestProduct destProduct;
public List<DestItem> destItems;
}
public class DestProduct{
public Integer nProductId;
public String sProductName;
}
public class DestItem{
public Integer nId;
public Integer nQty;
public String sDesc;
}
How do I tell Dozer to map Source to Destination?
You should check Dozer documentation. It has everything you need to map your classes.
I think you are worried mainly for below mappings:
1. Map custom object fields and wrapper classes fields:
Check the Basic property mapping in dozer documentation. Many data type coversions are performed automatically by the Dozer mapping engine. Check the below link for more info.
http://dozer.sourceforge.net/documentation/simpleproperty.html
2. List fields containing custom object mappings:
This is explained at the below link:
http://dozer.sourceforge.net/documentation/collectionandarraymapping.html#
For cases where a feature isn't supported out of the box, you can also write a custom converter:
http://dozer.sourceforge.net/documentation/customconverter.html
Also, It will help to first write simple standalone programs to understand/test a particular mapping before jumping with implementation in your project.

JAXB : Custom name for unmarshalled json array

I am trying to unmarshall a list of error objects.
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity(
new GenericEntity<List<Error>>(errors){})
.type(MediaType.APPLICATION_JSON_TYPE).build());
The unmarshalled JSON looks like
{"error":[{"code":5,"detail":"app level of request, 2, does not meet the minimum required app level, 5"}]}
However I want my json array to be named errors instead of error. This is the java class definition of Error
#XmlRootElement
public class Error {
#XmlElement
private Integer code;
#XmlElement
private String detail;
public Error(Integer code, String detail) {
this.code = code;
this.detail = detail;
}
public Error() {}
}
How can I achieve this?
By default, it uses #XmlRootElement. So you will need to use #JsonRootName
#JsonRootName("errors")
#XmlRootElement
public class Error {

JAXB and JFreeChart. It's possible?

I've readed about marshal and unmarshal complex objects and I would know if it's possible to use JAXB to create objects of JFreeChart API.
I've tryed myself but not works with the next code:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType( propOrder = { "id", "type", "renderer"})
public class Renderer {
#XmlElement
private Integer id;
#XmlElement
private Integer type;
#XmlElement
private XYItemRenderer renderer = new StandardXYItemRenderer();
// Getter's / Setter's
XYItemRenderer getRenderer() {
return this.renderer;
}
public Integer getId() {
return this.id;
}
public void setId(Integer dataset_id) {
this.id = dataset_id;
}
public Integer getType() {
return this.type;
}
public void setType(Integer dataset_type) {
this.type = dataset_type;
}
}
It compiles, but when try to unmarshal the file, throws the next exception:
WARNING [com.aws.chart]
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 14 counts of IllegalAnnotationExceptions
org.jfree.chart.renderer.xy.XYItemRenderer is an interface and jaxb can't handle interfaces.
this problem is related to the following location:
at org.jfree.chart.renderer.xy.XYItemRenderer
at private org.jfree.chart.renderer.xy.XYItemRenderer com.aws.chart.objectmodel.Renderer.renderer
at com.aws.chart.objectmodel.Renderer
at private com.aws.chart.objectmodel.Renderer com.aws.chart.objectmodel.Dataset.renderer
at com.aws.chart.objectmodel.Dataset
at private java.util.ArrayList com.aws.chart.objectmodel.Plot.datasets
at com.aws.chart.objectmodel.Plot
at private java.util.ArrayList com.aws.chart.objectmodel.Chart.plots
at com.aws.chart.objectmodel.Chart
.................
I'm doing something wrong or simply it's not possible to do it?
Thanks in advance.
So it can be possible to do it, but it is not simple as that.
Firstly the JFReeChart classes that are referenced must be serializable by jaxb. As you cannot add anotations to them they must be marshallable/unmarshallabe using only defaults jaxb settings. With the complicated class chierarchy/object structure it may not be possible.
The issue with the interface or with not working default mapping may be solved by usied XMLAdaptor that will handle the conversion.
http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html
You will have to work with it by try and fix error approach to see what elements causes problem for jaxb. It may be too much work to solve them all.
Other approach will be using Java normal serialization, you could serialize the JFreeChart objects, and then encode the serialized stream in xml field as BASE64 for example. Then your class after unmarshling, could deserialized that field to re-create JFreeChart object.
So the esiest would be to have in your class
#XMLElement
String serialRender;
#XmlTransient
XYItemRenderer renderer;
boolean beforeMarshal(Marshaller mar) {
serialRender = serializeToString(renderer);
}
void afterUnmarshal(Unmarshaller unm, Object parent) {
renderer = deserializeFromString(serialRender);
}

Categories