Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

EclipseLink/UserGuide/MOXy/Web Services

EclipseLink MOXy


Web Services

When using an instance of a class with a Web Service with the JAX-WS implementation, you can use fields/properties that hold binary data as a SOAP attachment. By using an attachment, you can send data outside the XML message. This helps to optimize the transaction, since binary data encoded as a xs:base64Binary string could be extremely large.

Use the following JAXB annotations that control this:

  • @XmlInlineBinaryData: Specifies that the binary data for this field/property must be written to the XML document as xs:base64Binary and not sent as an attachment.
  • @XmlMimeType: Used for properties of type java.awt.Image or javax.xml.transform.Source, allows the MIME type to be specified that will be used for encoding the data as bytes.


Java Model

This sample domain model has several properties to represent binary data:

  • Property c uses the @XmlInlineBinaryData to prevent that data from being treated as an attachment
  • Property d uses the @XmlMimeType to specify that the Image should be encoded as a JPEG.


package blog.attachments;
 
import java.awt.Image;
 
import javax.xml.bind.annotation.XmlInlineBinaryData;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement
public class Root {
 
    private byte[] a;
    private byte[] b;
    private byte[] c;
    private Image d;
 
    public byte[] getA() {
        return a;
    }
 
    public void setA(byte[] foo) {
        this.a = foo;
    }
 
    public byte[] getB() {
        return b;
    }
 
    public void setB(byte[] bar) {
        this.b = bar;
    }
 
    @XmlInlineBinaryData
    public byte[] getC() {
        return c;
    }
 
    public void setC(byte[] c) {
        this.c = c;
    }
 
    @XmlMimeType("image/jpeg")
    public Image getD() {
        return d;
    }
 
    public void setD(Image d) {
        this.d = d;
    }
 
}

Web Service Layer

When using a JAX-WS implementation that uses attachments, some data will be marshalled as xs:base64Binary and other data will be marshalled as an identifier serving as a reference to the attachment. The XML payload will appear similar to the following:

<root>
    <a>
        <xop:Include
            href="cid:1"
            xmlns:xop="http://www.w3.org/2004/08/xop/include"/>
    </a>
    <b>QkFS</b>
    <c>SEVMTE8gV09STEQ=</c>
    <d>
        <xop:Include
            href="cid:2"
            xmlns:xop="http://www.w3.org/2004/08/xop/include"/>
    </d>
</root>

A JAX-WS provider achieves this by leveraging JAXB's AttachmentMarshaller and AttachmentUnmarshaller mechanisms. You do not need to write any code to make this happen. The following code is provided to give you a behind the scenes look.

Example AttachmentMarshaller

A JAX-WS provider that wants to leverage attachments registers an implementation of javax.xml.bind.attachment.AttachmentMarshaller on the JAXB Marshaller. The implementation is specific to the JAX-WS provider, but below is a sample of how it might look. A JAX-WS provider can choose when to handle binary data as an attachment, in the implementation below any candidate byte[] of size greater than 10 will be treated as an attachment.

 
package blog.attachments;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.activation.DataHandler;
import javax.xml.bind.attachment.AttachmentMarshaller;
 
public class ExampleAttachmentMarshaller extends AttachmentMarshaller {
 
    private static final int THRESHOLD = 10;
 
    private List<Attachment> attachments = new ArrayList<Attachment>();
 
    public List<Attachment> getAttachments() {
        return attachments;
    }
 
    @Override
    public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
        return null;
    }
 
    @Override
    public String addMtomAttachment(byte[] data, int offset, int length, String mimeType, String elementNamespace, String elementLocalName) {
        if(data.length < THRESHOLD) {
            return null;
        }
        int id = attachments.size() + 1;
        attachments.add(new Attachment(data, offset, length));
        return "cid:" + String.valueOf(id);
    }
 
    @Override
    public String addSwaRefAttachment(DataHandler data) {
        return null;
    }
 
    @Override
    public boolean isXOPPackage() {
        return true;
    }
 
    public static class Attachment {
 
        private byte[] data;
        private int offset;
        private int length;
 
        public Attachment(byte[] data, int offset, int length) {
            this.data = data;
            this.offset = offset;
            this.length = length;
        }
 
        public byte[] getData() {
            return data;
        }
 
        public int getOffset() {
            return offset;
        }
 
        public int getLength() {
            return length;
        }
 
    }
 
}

Example AttachmentUnmarshaller

If a JAX-WS provider is leveraging attachments, then an implementation of javax.xml.bind.attachment.AttachmentUnmarshaller must be specified on the JAXB Unmarshaller. Again the implementations is specific to the JAX-WS provider. A sample implementation is shown below:

 
package blog.attachments;
 
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
 
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.bind.attachment.AttachmentUnmarshaller;
 
public class ExampleAttachmentUnmarshaller extends AttachmentUnmarshaller {
 
    private Map<String, byte[]> attachments = new HashMap<String, byte[]>();
 
    public Map<String, byte[]> getAttachments() {
        return attachments;
    }
 
    @Override
    public DataHandler getAttachmentAsDataHandler(String cid) {
        byte[] bytes = attachments.get(cid);
        return new DataHandler(new ByteArrayDataSource(bytes));
    }
 
    @Override
    public byte[] getAttachmentAsByteArray(String cid) {
        return attachments.get(cid);
    }
 
    @Override
    public boolean isXOPPackage() {
        return true;
    }
 
    private static class ByteArrayDataSource implements DataSource {
 
        private byte[] bytes;
 
        public ByteArrayDataSource(byte[] bytes) {
            this.bytes = bytes;
        }
 
        public String getContentType() {
            return  "application/octet-stream";
        }
 
        public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(bytes);
        }
 
        public String getName() {
            return null;
        }
 
        public OutputStream getOutputStream() throws IOException {
            return null;
        }
 
    }
 
}

Demo Code

The following example was inspired by an answer I gave on Stack Overflow (feel free to up vote). It covers how to leverage JAXB's AttachmentMarshaller & AttachmentUnmarshaller to produce a message in the following format:

[xml_length][xml][attach1_length][attach1]...[attachN_length][attachN]


While this example is unique to a particular use case, it does demonstrate JAXB's attachment mechanism without requiring a JAX-WS provider.

 
package blog.attachments;
 
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import javax.xml.bind.JAXBContext;
 
public class Demo {
 
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);
 
        Root root = new Root();
        root.setA("HELLO WORLD".getBytes());
        root.setB("BAR".getBytes());
        root.setC("HELLO WORLD".getBytes());
        root.setD(new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB));
 
        MessageWriter writer = new MessageWriter(jc);
        FileOutputStream outStream = new FileOutputStream("message.xml");
        writer.write(root, outStream);
        outStream.close();
 
        MessageReader reader = new MessageReader(jc);
        FileInputStream inStream = new FileInputStream("message.xml");
        Root root2 = (Root) reader.read(inStream);
        inStream.close();
        System.out.println(new String(root2.getA()));
        System.out.println(new String(root2.getB()));
        System.out.println(new String(root2.getC()));
        System.out.println(root2.getD());
    }
 
}


MessageWriter

 
package blog.attachments;
 
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
 
import blog.attachments.ExampleAttachmentMarshaller.Attachment;
 
public class MessageWriter {
 
    private JAXBContext jaxbContext;
 
    public MessageWriter(JAXBContext jaxbContext) {
        this.jaxbContext = jaxbContext;
    }
 
    /**
     * Write the message in the following format:
     * [xml_length][xml][attach1_length][attach1]...[attachN_length][attachN]
     */
    public void write(Object object, OutputStream stream) {
        try {
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
            ExampleAttachmentMarshaller attachmentMarshaller = new ExampleAttachmentMarshaller();
            marshaller.setAttachmentMarshaller(attachmentMarshaller);
            ByteArrayOutputStream xmlStream = new ByteArrayOutputStream();
            marshaller.marshal(object, xmlStream);
            byte[] xml = xmlStream.toByteArray();
            xmlStream.close();
 
            ObjectOutputStream messageStream = new ObjectOutputStream(stream);
 
            messageStream.writeInt(xml.length); //[xml_length]
            messageStream.write(xml); // [xml]
 
            for(Attachment attachment : attachmentMarshaller.getAttachments()) {
                messageStream.writeInt(attachment.getLength()); // [attachX_length]
                messageStream.write(attachment.getData(), attachment.getOffset(), attachment.getLength());  // [attachX]
            }
 
            messageStream.flush();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
 
}

MessageReader

 
package blog.attachments;
 
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
 
public class MessageReader {
 
    private JAXBContext jaxbContext;
 
    public MessageReader(JAXBContext jaxbContext) {
        this.jaxbContext = jaxbContext;
    }
 
    /**
     * Read the message from the following format:
     * [xml_length][xml][attach1_length][attach1]...[attachN_length][attachN]
     */
    public Object read(InputStream stream) {
        try {
            ObjectInputStream inputStream = new ObjectInputStream(stream);
            int xmlLength = inputStream.readInt();  // [xml_length]
 
            byte[] xmlIn = new byte[xmlLength];
            inputStream.read(xmlIn);  // [xml]
 
            ExampleAttachmentUnmarshaller attachmentUnmarshaller = new ExampleAttachmentUnmarshaller();
            int id = 1;
            while(inputStream.available() > 0) {
                int length = inputStream.readInt();  // [attachX_length]
                byte[] data = new byte[length];  // [attachX]
                inputStream.read(data);
                attachmentUnmarshaller.getAttachments().put("cid:" + String.valueOf(id++), data);
            }
 
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setAttachmentUnmarshaller(attachmentUnmarshaller);
            ByteArrayInputStream byteInputStream = new ByteArrayInputStream(xmlIn);
            Object object = unmarshaller.unmarshal(byteInputStream);
            byteInputStream.close();
            inputStream.close();
            return object;
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
 
}

Eclipselink-logo.gif
Version: 2.2.0 Draft
Other versions...

Copyright © Eclipse Foundation, Inc. All Rights Reserved.