- @XmlInlineBinaryData
This 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
For properties of type java.awt.Image or javax.xml.transform.Source, this annotation allows the mime type to be specified that will be used for encoding the data as bytes.
The following class will be used as the domain model for this example. It has various properties for representing binary data. Property "c" has been annotated with @XmlInlineBinaryData to prevent that data from being treated as an attachment, and property "d" has been annotated with @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 a JAX-WS implementation leverages attachments the XML payload will look similar to the following. Some data will be marshalled as xs:base64Binary and other data will be marshalled as an identifier that will serve as a reference to the attachment.
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.
When a JAX-WS implementation leverages attachments the XML payload will look similar to the following. Some data will be marshalled as xs:base64Binary and other data will be marshalled as an identifier that will serve as a reference to the attachment.
<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:
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:
While this example is unique to a particular use case, it does demonstrate JAXB's attachment mechanism without requiring a JAX-WS provider.
[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.
Demo
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); } } }
Hi Blaise,
ReplyDeleteThanks for the informative blog!
Just one question:
if I generate my Domain model from a schema and the schema defines my binary tag as
"", then the generated model output is
"@XmlElement(name = "BitmapData", required = true)
protected byte[] bitmapData;"
I have to explicitly add "@XmlInlineBinaryData" to ensure that the binary data is not sent as an attachment.
Do you know of a schema deceleration that ensure that the "@XmlInlineBinaryData" is added to the domain model when it is generated.
Thanks,
Nishern
I have a new post answering this question:
ReplyDelete- Schema to Java: @XmlMimeType & @XmlInlineBinaryData
-Blaise
Excelent Post!
ReplyDeleteThanks for save me a lot of wasted time,
Cheers