|
|
(One intermediate revision by the same user not shown) |
Line 1: |
Line 1: |
− | {{EclipseLink_UserGuide
| + | '''[[Image:Elug_draft_icon.png|Warning]] This page is obsolete. Please see ''[http://www.eclipse.org/eclipselink/documentation/ Developing JAXB Applications Using EclipseLink MOXy]'' for current information.''' |
− | |info=y
| + | |
− | |toc=n
| + | |
− | |eclipselink=y
| + | |
− | |eclipselinktype=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.
| + | |
− | | + | |
− | | + | |
− | <source lang="java">
| + | |
− | | + | |
− | 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;
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− | </source>
| + | |
− | | + | |
− | ==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:
| + | |
− | | + | |
− | <source lang="xml">
| + | |
− | | + | |
− | <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>
| + | |
− | </source>
| + | |
− | | + | |
− | 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.
| + | |
− | <source lang="java">
| + | |
− |
| + | |
− | 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;
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− | | + | |
− | </source>
| + | |
− | | + | |
− | ===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:
| + | |
− | | + | |
− | <source lang="java">
| + | |
− |
| + | |
− | 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;
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− | | + | |
− | </source>
| + | |
− | | + | |
− | ==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.
| + | |
− | | + | |
− | <source lang="java">
| + | |
− |
| + | |
− | 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());
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− | </source>
| + | |
− | | + | |
− | | + | |
− | ===MessageWriter===
| + | |
− | <source lang="java">
| + | |
− |
| + | |
− | 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);
| + | |
− | }
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− | </source>
| + | |
− | | + | |
− | ===MessageReader===
| + | |
− | | + | |
− | <source lang="java">
| + | |
− |
| + | |
− | 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);
| + | |
− | }
| + | |
− | }
| + | |
− |
| + | |
− | }
| + | |
− | </source>
| + | |
− | | + | |
− | {{EclipseLink_MOXy
| + | |
− | |up= [[EclipseLink/UserGuide/MOXy|MOXy User Guide]]
| + | |
− | |previous=[[EclipseLink/UserGuide/MOXy/Mapping_JPA_Entities_to_XML|Mapping JPA Entities to XML]]
| + | |
− | |next=n
| + | |
− | |version=2.2.0 Draft}}
| + | |