I have a web server responding with xml data and a client consuming it. Both share the same domain code. One of the domain objects looks like this:
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlRootElement(name = "image")
public class Image {
private String filename;
private ImageTypeEnum type;
@XmlElement(name = "imageUri")
public String getAbsoluteUri() {
// some complex computation
return uri;
}
}
When I try to unmarshal the response from the server into this o开发者_StackOverflow社区bject, since there's no setter for absoluteUri, I don't have the imageUri in the class. So I extend it like this:
public class FEImage extends Image{
private String imageUri;
public String getAbsoluteUri() {
return imageUri;
}
public void setAbsoluteUri(String imageUri) {
this.imageUri = imageUri;
}
}
My ObjectFactory
@XmlRegistry
public class ObjectFactory {
public Image createImage(){
return new FEImage();
}
}
My code to unmarshal is here:
JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.sun.xml.bind.ObjectFactory",new ObjectFactory());
((JAXBElement)unmarshaller.unmarshal((InputStream) response.getEntity())).getValue();
However, the setAbsoluteUri
doesn't seem to be getting called in FEImage
while unmarshalling. When I add a dummy setAbsoluteUri
in Image.java
, everything works as expected.
Can someone tell me how can I cleanly extend from Image.java
?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
A JAXB implementation is not required to use the ObjectFactory
class when instantiating an object. You can configure instantiation to be done via a factory class using the @XmlType
annotation:
@XmlType(factoryClass=ObjectFactory.class, factoryMethod="createImage")
public class Image {
private String filename;
private ImageTypeEnum type;
@XmlElement(name = "imageUri")
public String getAbsoluteUri() {
// some complex computation
return uri;
}
}
- http://blog.bdoughan.com/2011/06/jaxb-and-factory-methods.html
If you do the above, then your JAXB implementation will still use the Image
class to derive the metadata so it will not solve your problem. An alternate approach would be to use an XmlAdapter
for this use case:
- http://blog.bdoughan.com/2010/12/jaxb-and-immutable-objects.html
Better still, when a property on your domain object does not have a setter, you can tell your
JAXB implementation (EclipseLink MOXy, Metro, Apache JaxMe, etc) to use field (instance variable) access instead using @XmlAccessorType(XmlAccessType.FIELD)
:
@XmlAccessorType(XmlAccessType.FIELD)
public class Image {
}
- http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
UPDATE #1
If you are not able to modify the domain objects, then you may be interested in MOXy's externalized metadata. This extension provides a means via XML to provide JAXB metadata for classes where you cannot modify the source.
For More Information
- http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
- http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings
UPDATE #2 - Based on results of chat
Image
Below is the implementation of the Image
class that I will use for this example. For the complex computation of getAbsoluteUri()
I simply add the prefix "CDN" to the filename:
package forum7552310;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlRootElement(name = "image")
public class Image {
private String filename;
private ImageTypeEnum type;
@XmlElement(name = "imageUri")
public String getAbsoluteUri() {
return "CDN" + filename;
}
}
binding.xml
Below is the MOXy binding document I put together. In this file I do a few things:
- Set
XmlAccessorType
toFIELD
- Mark the absoluteURI property to be
XmlTransient
since we will be mapping thefilename
field instead. - Specify that an XmlAdapter will be used with the
filename
field. This is to apply the logic that is done in thegetAbsoluteUri()
method.
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum7552310">
<java-types>
<java-type name="Image" xml-accessor-type="FIELD">
<java-attributes>
<xml-element java-attribute="filename" name="imageUri">
<xml-java-type-adapter value="forum7552310.FileNameAdapter"/>
</xml-element>
<xml-transient java-attribute="absoluteUri"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
FileNameAdapter
Below is the implementation of the XmlAdapter
that applies the same name algorithm as the getAbsoluteUri()
method:
package forum7552310;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class FileNameAdapter extends XmlAdapter<String, String> {
@Override
public String marshal(String string) throws Exception {
return "CDN" + string;
}
@Override
public String unmarshal(String adaptedString) throws Exception {
return adaptedString.substring(3);
}
}
Demo
Below is the demo code demonstrating how to apply the binding file when creating the JAXBContext:
package forum7552310;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum7552310/binding.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Image.class}, properties);
File xml = new File("src/forum7552310/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Image image = (Image) unmarshaller.unmarshal(xml);
System.out.println(image.getAbsoluteUri());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(image, System.out);
}
}
jaxb.properties
You need to include a file named jaxb.properties
with the following contents in the same package as your Image
class:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
input.xml
Here is the XML input I used:
<?xml version="1.0" encoding="UTF-8"?>
<image>
<imageUri>CDNURI</imageUri>
</image>
Output
And here is the output from running the demo code:
CDNURI
<?xml version="1.0" encoding="UTF-8"?>
<image>
<imageUri>CDNURI</imageUri>
</image>
精彩评论