开发者

Add/Override behavior on Jaxb generated classes by extending them

开发者 https://www.devze.com 2023-04-07 06:05 出处:网络
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:

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 to FIELD
  • Mark the absoluteURI property to be XmlTransient since we will be mapping the filename field instead.
  • Specify that an XmlAdapter will be used with the filename field. This is to apply the logic that is done in the getAbsoluteUri() 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>
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号