1

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 object, 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?

Radek Postołowicz
  • 4,506
  • 2
  • 30
  • 47
Sam
  • 99
  • 1
  • 2
  • 7

1 Answers1

5

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;
    }
}

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:

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 {
}

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


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>
Pyves
  • 6,333
  • 7
  • 41
  • 59
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • That may not be possible as I do not have control over Image.java. Can this be done on FEImage.java? Moreover, notice that absoluteUri is NOT an instance level field in Image.java. It is generated on the fly. – Sam Sep 26 '11 at 18:08
  • @Sam - I have added more detail to my answer. – bdoughan Sep 26 '11 at 19:45
  • Thanks, this looks promising. I've started to implement this but keep running into javax.xml.bind.JAXBException: property "eclipselink-oxm-xml" is not supported. I have specified jaxb.properties in the same location as FEImage.java, which is com.example.fe.domain.image. I also tried putting it one level above and also in the classpath, to no avail. Any hints? – Sam Sep 27 '11 at 09:10
  • Do you have the necessary EclipseLink jars on your classpath? Are you running inside or outside an application server? – bdoughan Sep 27 '11 at 09:45
  • Yes, I have all the necessary eclipselink jars in my classpath. running inside tomcat 6. – Sam Sep 27 '11 at 16:57
  • @Sam - Since you will be using the metadata file to map the `Image` class you need to put the mappings document in the same package as the `Image` class. For more information see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html – bdoughan Sep 27 '11 at 17:08
  • I do not have access to the Image class, it comes in as a jar. I've extended Image into FEImage. I'm trying to convert an xml that I receive from the server into FEImage. I've added a method in FEImage and I've mapped it in the bindings.xml. I am expecting to take advantage of JAXB annotations on Image as well as the XML bindings on FEImage in this way. Also, I've added a jaxb.properties in the same package as FEImage. I tested it with System.out.println(JAXBContext.newInstance(FEImage.class).getClass()); However, I still get class com.sun.xml.bind.v2.runtime.JAXBContextImpl – Sam Sep 27 '11 at 18:20
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/3836/discussion-between-sam-and-blaise-doughan) – Sam Sep 27 '11 at 18:20
  • @Sam - Based on the results of our chat I have updated my answer with a more detailed example. – bdoughan Sep 27 '11 at 20:35