Create Null Object Unmarshalling Empty Element with JAXB - java

I am using JAXB (EclipseLink implementation) in a JAX-RS webservice. When an empty element is passed in the XML request an empty object is created. Is it possible to set JAXB to create a null object instead?
Example XML:
<RootEntity>
<AttributeOne>someText</AttributeOne>
<EntityOne id="objectID" />
<EntityTwo />
</RootEntity>
When unmarshalling, an instance of EntityOne is created and the id attribute set to "objectID" and an instance of EntityTwo is created with null attributes. Instead I would like a null object for EntityTwo as having an empty object is causing me problems with JPA persistence operations.

You can specify this behaviour using MOXy's NullPolicy. You will need to create a DescriptorCustomizer to modify the underlying mappings. Don't worry it's easier than it sounds, I'll demonstrate below:
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;
import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType;
public class RootEntityCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
XMLCompositeObjectMapping entityTwoMapping = (XMLCompositeObjectMapping) descriptor.getMappingForAttributeName("entityTwo");
entityTwoMapping.getNullPolicy().setNullRepresentedByEmptyNode(true);
entityTwoMapping.getNullPolicy().setMarshalNullRepresentation(XMLNullRepresentationType.EMPTY_NODE);
}
}
Below is how you associate the customizer with your model class:
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlCustomizer;
#XmlRootElement(name="RootEntity")
#XmlCustomizer(RootEntityCustomizer.class)
public class RootEntity {
private String attributeOne;
private Entity entityOne;
private Entity entityTwo;
#XmlElement(name="AttributeOne")
public String getAttributeOne() {
return attributeOne;
}
public void setAttributeOne(String attributeOne) {
this.attributeOne = attributeOne;
}
#XmlElement(name="EntityOne")
public Entity getEntityOne() {
return entityOne;
}
public void setEntityOne(Entity entityOne) {
this.entityOne = entityOne;
}
#XmlElement(name="EntityTwo")
public Entity getEntityTwo() {
return entityTwo;
}
public void setEntityTwo(Entity entityTwo) {
this.entityTwo = entityTwo;
}
}
In the next version of MOXy (2.2) you will be able to do this via annotations.
#XmlElement(name="EntityTwo")
#XmlNullPolicy(emptyNodeRepresentsNull=true,
nullRepresentationForXml=XmlMarshalNullRepresentation.EMPTY_NODE)
public Entity getEntityTwo() {
return entityTwo;
}
You can try this now with one of the EclipseLink 2.2.0 nightly builds:
http://www.eclipse.org/eclipselink/downloads/nightly.php

Related

Saving/updating data with spring

I get an javax.persistence.EntityNotFoundException from the following code:
#Transactional
public ManagementEmailConfig save(ManagementEmailConfig managementEmailConfig)
{
logger.info("Save Management Email Config");
try
{
managementEmailConfig = entityManager.merge(managementEmailConfig);
entityManager.flush();
} catch (Exception e)
{
//ERROR: com.xxx.app.dao.kpi.ManagementEmailConfigDAO -
Not able to save Management Email Config
//javax.persistence.EntityNotFoundException: Unable to find com.xxx.app.model.configuration.AlertCommunicationAddress with id 1260
logger.error("Not able to save Management Email Config", e);
return null;
}
return managementEmailConfig;
}
where the model looks like this (shortened version):
import java.io.Serializable;
import javax.persistence.*;
import java.util.List;
/**
* The persistent class for the MANAGEMENT_EMAIL_CONFIG database table.
*
*/
#Entity
#Table(name="MANAGEMENT_EMAIL_CONFIG")
#NamedQuery(name="ManagementEmailConfig.findAll", query="SELECT m FROM ManagementEmailConfig m")
public class ManagementEmailConfig implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="MANAGEMENT_EMAIL_CONFIG_ID")
private long managementEmailConfigId;
//bi-directional many-to-one association to AlertCommunicationAddress
#OneToMany(mappedBy="managementEmailConfig")
private List<AlertCommunicationAddress> alertCommunicationAddresses;
public ManagementEmailConfig() {
}
public long getManagementEmailConfigId() {
return this.managementEmailConfigId;
}
public void setManagementEmailConfigId(long managementEmailConfigId) {
this.managementEmailConfigId = managementEmailConfigId;
}
public List<AlertCommunicationAddress> getAlertCommunicationAddresses() {
return this.alertCommunicationAddresses;
}
public void setAlertCommunicationAddresses(List<AlertCommunicationAddress> alertCommunicationAddresses) {
this.alertCommunicationAddresses = alertCommunicationAddresses;
}
public AlertCommunicationAddress addAlertCommunicationAddress(AlertCommunicationAddress alertCommunicationAddress) {
getAlertCommunicationAddresses().add(alertCommunicationAddress);
alertCommunicationAddress.setManagementEmailConfig(this);
return alertCommunicationAddress;
}
public AlertCommunicationAddress removeAlertCommunicationAddress(AlertCommunicationAddress alertCommunicationAddress) {
getAlertCommunicationAddresses().remove(alertCommunicationAddress);
alertCommunicationAddress.setManagementEmailConfig(null);
return alertCommunicationAddress;
}
}
The use case is that the user provides a new alertCommunicationAddress to an existing ManagementEmailConfig and I want create the alertCommunicationAddress then update the ManagementEmailConfig.
If you are using Spring you've made life really difficult for yourself by not using Spring features
I suggest you do the following:
Using Spring Data JPA, write a repository interface to allow
you to easily persist your entity:
public interface ManagementEmailConfigRepository extends JpaRepository { }
use it to persist your entity (save is insert if it's not there,
update if it is)
#Inject
private ManagementEmailConfigRepository managementEmailConfigRepository;
....
managementEmailConfigRepository.save(managementEmailConfig);
This gets rid of the following from your code:
needing to write a save method at all
needing to do a flush
no need for try catch type code
no need for that named query on your entity
(you get that for free on your repository)
I'll leave it up to you to decide where you want the #Transactional annotation; it really shouldn't be on your DAO layer but higher up, e.g. your service layer.

override #XmlElement annotation with FIELD accessor type

I have a class with #XmlAccessorType(XmlAccessType.FIELD) annotation, and each private and protected field is annotated with #XmlElement(name='...').
The challenge: I may want to rename one of the xml element names in a later stage. This leads me to the question. Is there a way to override/redefine these annotations, if I create a sub-class ?
I believe that some implementations of JaxB allow for XML configuration to override the annotations. In this case this may actually be possible. Here is an article from Eclipslink explaining how this can be done http://www.eclipse.org/eclipselink/documentation/2.4/solutions/jpatoxml004.htm
In my opinion you can just build an XML configuration for the JaxB file you want to override.
I tried first with the #XmlAccessorType(XmlAccessType.FIELD) and to hide with #XmlTransient. This only works, if you mark the field in the superclass and in the child class with #XmlTransient. But I assume, this is not what you want.
As second approach I've tried with more restrictive #XmlAccessorType(XmlAccessType.PROPERTY) in the superclass and #XmlAccessorType(XmlAccessType.NONE) in the child class. See here my example:
package com.so.example;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/myresource")
public class MyResource {
#GET
#Path("/car")
#Produces(MediaType.APPLICATION_XML)
public Car getCar() {
Car car = new Car();
car.setWheels(4);
return car;
}
#GET
#Path("/suv")
#Produces(MediaType.APPLICATION_XML)
public Suv getSuv() {
Suv suv = new Suv();
List<String> bigWheels = new ArrayList<>();
bigWheels.add("left front wheel");
bigWheels.add("right front wheel");
bigWheels.add("left rear wheel");
bigWheels.add("right rear wheel");
suv.setBigWheels(bigWheels);
return suv;
}
}
Class Car:
package com.so.example;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement
public class Car {
protected Integer wheels;
public Car() {
}
#XmlElement(name = "wheels", nillable = true)
public Integer getWheels() {
return wheels;
}
public void setWheels(Integer wheels) {
this.wheels = wheels;
}
}
Class Suv (Child):
package com.so.example;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
public class Suv extends Car {
#XmlTransient
private Integer wheels;
private List<String> bigWheels;
public Suv() {
}
#Override
#XmlTransient
public Integer getWheels() {
return wheels;
}
#Override
public void setWheels(Integer wheels) {
this.wheels = wheels;
}
#XmlElement
public List<String> getBigWheels() {
return bigWheels;
}
public void setBigWheels(List<String> bigWheels) {
this.bigWheels = bigWheels;
}
}
One way to "hide" the element wheels of the superclass would be to mark it as "nillable=true" and not use primitive types. In this case, the field wheels will be marshalled to <wheels xsi:nil="true"/>
If it's possible for you to not use the parent class for marshalling and you are only using child classes, you could use the approach described here:
http://blog.bdoughan.com/2011/06/ignoring-inheritance-with-xmltransient.html
Also you could use moxy and specify a custom binding:
http://www.eclipse.org/eclipselink/documentation/2.4/moxy/runtime003.htm
Whilst in java, to my knowledge, overriding an annotation #XmlElement(name='...') to change the name property is not possible; you can create a global variable in your code and either pass it through your classes or your functions following the #XmlElement(name='...').
In the code below I created a single class but it contains the setter and getter methods required if you want to pass it through to another class
#XMLAccessorType(XMLAccessType.FIELD)
public class YourClass {
#XmlTransient
private String string = ""; //This can be replaced with whatever variable you are manipulating
//That could be an int or a file or anything really
#XmlElement(name = "your_name")
private void doSomething() {
String temp = getString(); //This variable is normally used to pass between different
//classes but may as well use it if you have one
//Your code which manipulates the String
setString(temp); //This variable is normally used to pass between different classes but
//may as well use it if you have one
}
#XmlElement(name = "your_other_name")
private void doSomethingElse() {
String temp = getString();
//Your code which manipulates the String
setString(temp);
}
public void getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
I would reccomend looking at the Java Docs for #XmlTransient and these two other relevant SO questions.
How to override JAXB #XMLAccessorType(XMLAccessType.FIELD) specified at a Class level with #XMLElement on a getter method for a property?
Jaxb - Overriding the XMLElement name attribute

MOXy is only marshaling base class members in a collection

I have a base class declared something like:
package com.mystuff.surrogates;
import java.io.Serializable;
import java.util.UUID;
public class BaseClass implements Serializable {
private UUID id;
private String name;
public UUID getId() { return this.id; }
public void setId(UUID id) { this.id = id; }
public String getName() { return thisname; }
public void setName(String name) { this.name = name; }
}
And a derived class which looks something like:
package com.mystuff.surrogates;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class DerivedClass extends BaseClass {
private String email;
public String getEmail() { return this.email; }
public void setEmail(String email) { this.email = email; }
}
Finally I have a class which I am trying to return as an object from a RESTful webservice call which includes a collection of BaseClass derived class instances and looks rather like:
package com.mystuff.surrogates;
import java.util.ArrayList;
import java.util.List;
public class Response {
List<BaseClass> objectList;
public List<BaseClass> getObjectList() { return this.objectList; }
public void setObjectList(List<BaseClass> objectList) { this.objectList = objectList; }
public void addObject(BaseClass obj) {
if (this.objectList == null) {
this.objectList = new ArrayList<>();
}
this.objectList.add(obj);
}
}
When marshaling this into either XML or JSON, only the members in the base class are included. How do I get Jersey / MOXy to marshal the entire class instance rather than just the base class members? While I have only shown here one derived class, I have several others I would like to potentially chose from to return in the list, so simply using List<DerivedClass> isn't an option since it would preclude returning any of those other classes in the list.
Other information:
Netbeans 8.0.2
Glassfish 4.1 (locally hosted)
Oracle JDK 8U31 (64 bit Windows)
Java EE 7
You'll need to use #XmlSeeAlso so the other classes are binded.
#XmlSeeAlso({DerivedClass.class})
public class BaseClass {
This may not get you the exact desired result, as the marshalled data will have a reference to the type. For instance with XML, you will see
<objectList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="derivedClass">
and JSON you will see
{"objectList":[{"type":"derivedClass",...
You can have a look at this answer for an idea of how to get rid of the type property if it's undesired.
As far as the JSON is concerned, using Jackson will not have this behavior. You can simply use jersey-media-json-jackson, which Glassfish also comes shipped with (you can add it as a dependency in a provided scope), and just register the JacksonFeature with the application.

Applying different Jackson filter for different Jersey REST service calls

I am using Jersey to implement JAX-RS REST-style services along with Jackson 2.0.2 for the JSON mapping. One of these REST services returns a List<EntityA> (let's call it indexA) where EntityA contains another List<EntityB> whereas another service just returns a List<EntityB> (let's call it indexB):
#Entity
#JsonAutoDetect
public class EntityA {
#Id
private String id;
#OneToMany
private List<EntityB> b;
...
}
#Entity
#JsonAutoDetect
#JsonFilter("bFilter")
public class EntityB {
#Id
private String id;
private String some;
private String other;
private String attributes;
...
}
#Path("/a")
public class AResource {
#GET
#Path("/")
public List<EntityA> indexA() {
...
}
}
#Path("/b")
public class BResource {
#GET
#Path("/")
public List<EntityB> indexB() {
...
}
}
What I'd like to achieve is to apply a Jackson filter to the indexA invocation so that not all attributes of the child EntityB elements are serialized. OTOH, indexB should return EntityB in its completeness.
I am aware of the existence of a ContextResolver<ObjectMapper>, which I am already using for other purposes. Unfortunately, for the ContextResolver it seems to be impossible to distinguish both service invocations as the Class supplied to ContextResolver.getContext(Class) is ArrayList in both cases (and thanks to type erasure I cannot figure out the generic type parameters).
Are there any hooks better suited at configuring an ObjectMapper/FilterProvider depending on the entity type that is being mapped?
I could use the approach proposed in How to return a partial JSON response using Java?: Manually mapping to a String, but that kills the whole beauty of a declarative annotation-based approach, so I'd like to avoid this.
I was in the same situation, after tons of research, I figured it out, the solution is to use #JsonView and Spring which can inject an ObjectMapper into the JSON Writer without killing the beauty of Jersey.
I am working on a set of REST APIs, I want to get a list of instances of SystemObject and the detail a specific instance of SystemObject, just like you I just want very limited of number of properties of each instance in the list and some additional properties in the detail, I just define Views for them, and add annotation in the SystemObject class. but by default, all properties with no #JsonView annotation will be output to the JSON, but there is a configuration item(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION) I can use to exclude them.
The problem is that I have to set it to true to meet my need. but I can not change the ObjectMapper which does the magic to convert the object to JSON, by reading the 3 articles below, I got the idea that the only way I can do is to inject a Modified ObjectMapper to Jersey.
Now I got what I want.
It is like you create multiple views against a database table.
These 3 links will help you in different ways:
How to create a ObjectMapperProvider which can be used by Spring to inject
Jersey, Jackson, Spring and JSON
Jersey + Spring integration example
REST resource:
package com.john.rest.resource;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;
import org.codehaus.jackson.map.annotate.JsonView;
import org.springframework.stereotype.Component;
import com.midtronics.esp.common.EspException;
import com.midtronics.esp.common.SystemObject;
import com.midtronics.esp.mobile.model.SystemObjectView;
import com.midtronics.esp.model.accesscontrol.AccessControlBean;
import com.midtronics.esp.model.site.SiteBean;
#Component
#Path("/hierarchy")
public class Hierarchy {
// Allows to insert contextual objects into the class,
// e.g. ServletContext, Request, Response, UriInfo
#Context
UriInfo uriInfo;
#Context
Request request;
// Return the list of sites
#GET
#Path("sites")
#Produces(MediaType.APPLICATION_JSON)
#JsonView({SystemObjectView.ObjectList.class})
public List<SystemObject> listSite(
#HeaderParam("userId") String userId,
#HeaderParam("password") String password) {
ArrayList<SystemObject> sites= new ArrayList<SystemObject>();
try{
if(!AccessControlBean.CheckUser(userId, password)){
throw new WebApplicationException(401);
}
SystemObject.GetSiteListByPage(sites, 2, 3);
return sites;
} catch(EspException e){
throw new WebApplicationException(401);
} catch (Exception e) {
throw new WebApplicationException(500);
}
}
// Return the number of sites
#GET
#Path("sites/total")
#Produces(MediaType.TEXT_PLAIN)
public String getSiteNumber(#HeaderParam("userId") String userId,
#HeaderParam("password") String password) {
try{
return Integer.toString(SiteBean.GetSiteTotal());
} catch(EspException e){
throw new WebApplicationException(401);
} catch (Exception e) {
throw new WebApplicationException(500);
}
}
}
REST model:
package com.john.rest.model;
import java.io.Serializable;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlRootElement;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonView;
import com.midtronics.esp.mobile.model.SystemObjectView;
import com.midtronics.esp.model.common.ICommonDAO;
#XmlRootElement
public class SystemObject implements Serializable
{
private static final long serialVersionUID = 3989499187492868996L;
#JsonProperty("id")
#JsonView({SystemObjectView.ObjectList.class, SystemObjectView.ObjectDetail.class})
protected String objectID = "";
#JsonProperty("parentId")
protected String parentID = "";
#JsonProperty("name")
#JsonView({SystemObjectView.ObjectList.class, SystemObjectView.ObjectDetail.class})
protected String objectName = "";
//getters...
//setters...
}
REST model view:
package com.john.rest.model;
public class SystemObjectView {
public static class ObjectList { };
public static class ObjectDetail extends ObjectList { }
}

XmlAdapter not working as expected in JAXB RI

I am trying to implement a XmlAdapter for modifying the marshalling/unmarshalling of certain object properties. Particularly, I tried with the NullStringAdapter described here:
Jaxb: xs:attribute null values
The objective of the NullStringAdapter is marshalling null values as empty strings, and viceversa.
The only difference with the example described above and my code, is that I want to apply the adapter to an element, not to an attribute, so what I have is:
#XmlElement
#XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue; //someValue could be null, in that case the adapter should marshall it as an empty string
}
However, after some debugging, I realized that the Adapter methods are never called during the marshalling from Java to XML!. This occurs when the XmlElement value is null.
When this value is different than null, the adapter methods are called as expected.
Thanks for any help!.
Note: I'm the EclipseLink JAXB (MOXy) lead, and a member of the JAXB 2 (JSR-222) expert group.
However, after some debugging, I realized that the Adapter methods are
never called during the marshalling from Java to XML!. This occurs
when the XmlElement value is null. When this value is different than
null, the adapter methods are called as expected.
This behaviour varies between implementations of JAXB. The JAXB reference implementation will not call the marshal method on the XmlAdapter when the field/property is null, but MOXy will.
What the JAXB spec says (section 5.5.1 Simple Property)
The get or is method returns the property’s value as specified in the
previous subsection. If null is returned, the property is considered
to be absent from the XML content that it represents.
The MOXy interpretation of this statement is that the value of the field/property is really the value once it has gone through the XmlAdapter. This is necessary to support the behaviour that Sergio is looking for.
Of course the adapter will never be called if there isn't any element in the input to trigger that action. What happens in that example you're linked is that an attribute with an empty value is presented:
<element att="" />
The key here is that there is an att attribute, but it has an empty String. So a JAXB unmarshaller is gonna present that to the setter. But, since there's an adapter declared on it, it will pass through there and get turned into a null value.
But if you had this
<element />
it's another story. There's no att attribute, so the setter would never need to be called.
There's a difference between an element that occurs but has no content and a complete absence of an element. The former can basically be considered to contain an empty String, but the latter is just "not there".
EDIT: tested with these classes...
Bean.java
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="Test")
public class Bean {
#XmlElement
#XmlJavaTypeAdapter(NullStringAdapter.class)
private String someValue;
public Bean() {
}
public String getSomeValue() {
return someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
NullStringAdapter.java
package jaxbadapter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class NullStringAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(final String v) throws Exception {
if("".equals(v)) {
return null;
}
return v;
}
#Override
public String marshal(final String v) throws Exception {
if(null == v) {
return "";
}
return v;
}
}
ObjectFactory.java
package jaxbadapter;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
public ObjectFactory() {
}
public Bean createBean() {
return new Bean();
}
#XmlElementDecl(name = "Test")
public JAXBElement<Bean> createTest(Bean value) {
return new JAXBElement<>(new QName("Test"), Bean.class, null, value);
}
}
Main.java
package jaxbadapter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.transform.stream.StreamResult;
public class Main {
public static void main(String[] args) throws Exception {
final JAXBContext context = JAXBContext.newInstance("jaxbadapter");
final Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
final ObjectFactory of = new ObjectFactory();
final Bean b1 = new Bean();
final Bean b2 = new Bean();
b2.setSomeValue(null);
final Bean b3 = new Bean();
b3.setSomeValue("");
m.marshal(of.createTest(b1), System.out);
System.out.println("");
m.marshal(of.createTest(b2), System.out);
System.out.println("");
m.marshal(of.createTest(b3), System.out);
System.out.println("");
}
}
This is the output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test>
<someValue></someValue>
</Test>
Actually surprised me quite a bit. I've then tried changing the getter to return someValue == null ? "" : someValue; to no avail. Then set a breakpoint on the getter and found out it never gets called.
Apparently JAXB uses reflection to try and retrieve the value rather than going through the setter when using XmlAccessType.FIELD. Hardcore. Now, you can bypass this by using XmlAccessType.PROPERTY instead and annotating either the getter or setter...
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(name="Test")
public class Bean {
private String someValue;
public Bean() {
}
#XmlElement
#XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
... but that still didn't help. The adapter's marshal method was only called once, on the last test case where an empty String had been set. Apparently it first calls the getter and when that returns null, it simply skips the adapter stuff altogether.
The only solution I can come up with is just foregoing the use of an adapter altogether here and put the substitution in the getter, making sure to use XmlAccessType.PROPERTY:
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(name="Test")
public class Bean {
private String someValue;
public Bean() {
}
#XmlElement
// #XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue == null ? "" : someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
That worked for me. It's only really an option if you're creating the JAXB-annotated classes yourself and not generating them via XJC from a schema, though.
Maybe someone can clarify a bit why adapters are skipped for nulls and if there's a way to change that behaviour.

Categories

Resources