1/*
2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.ws.message.jaxb;
27
28import com.sun.istack.internal.FragmentContentHandler;
29import com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer;
30import com.sun.xml.internal.stream.buffer.XMLStreamBuffer;
31import com.sun.xml.internal.stream.buffer.XMLStreamBufferResult;
32import com.sun.xml.internal.ws.api.SOAPVersion;
33import com.sun.xml.internal.ws.api.message.AttachmentSet;
34import com.sun.xml.internal.ws.api.message.Header;
35import com.sun.xml.internal.ws.api.message.HeaderList;
36import com.sun.xml.internal.ws.api.message.Message;
37import com.sun.xml.internal.ws.api.message.MessageHeaders;
38import com.sun.xml.internal.ws.api.message.StreamingSOAP;
39import com.sun.xml.internal.ws.encoding.SOAPBindingCodec;
40import com.sun.xml.internal.ws.message.AbstractMessageImpl;
41import com.sun.xml.internal.ws.message.AttachmentSetImpl;
42import com.sun.xml.internal.ws.message.RootElementSniffer;
43import com.sun.xml.internal.ws.message.stream.StreamMessage;
44import com.sun.xml.internal.ws.spi.db.BindingContext;
45import com.sun.xml.internal.ws.spi.db.BindingContextFactory;
46import com.sun.xml.internal.ws.spi.db.XMLBridge;
47import com.sun.xml.internal.ws.streaming.XMLStreamWriterUtil;
48import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
49import com.sun.xml.internal.org.jvnet.staxex.util.MtomStreamWriter;
50import com.sun.xml.internal.ws.util.xml.XMLReaderComposite;
51import com.sun.xml.internal.ws.util.xml.XMLReaderComposite.ElemInfo;
52
53import org.xml.sax.ContentHandler;
54import org.xml.sax.ErrorHandler;
55import org.xml.sax.SAXException;
56
57import javax.xml.bind.JAXBContext;
58import javax.xml.bind.JAXBElement;
59import javax.xml.bind.JAXBException;
60import javax.xml.bind.Marshaller;
61import javax.xml.bind.Unmarshaller;
62import javax.xml.bind.attachment.AttachmentMarshaller;
63import javax.xml.bind.annotation.XmlRootElement;
64import javax.xml.bind.util.JAXBResult;
65import javax.xml.namespace.QName;
66import javax.xml.stream.XMLStreamException;
67import javax.xml.stream.XMLStreamReader;
68import javax.xml.stream.XMLStreamWriter;
69import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
70import javax.xml.transform.Source;
71import javax.xml.ws.WebServiceException;
72import java.io.OutputStream;
73import java.util.List;
74
75/**
76 * {@link Message} backed by a JAXB bean.
77 *
78 * @author Kohsuke Kawaguchi
79 */
80public final class JAXBMessage extends AbstractMessageImpl implements StreamingSOAP {
81    private MessageHeaders headers;
82
83    /**
84     * The JAXB object that represents the payload.
85     */
86    private final Object jaxbObject;
87
88    private final XMLBridge bridge;
89
90    /**
91     * For the use case of a user-supplied JAXB context that is not
92     * a known JAXB type, as when creating a Disaptch object with a
93     * JAXB object parameter, we will marshal and unmarshal directly with
94     * the context object, as there is no Bond available.  In this case,
95     * swaRef is not supported.
96     */
97    private final JAXBContext rawContext;
98
99    /**
100     * Lazily sniffed payload element name
101     */
102    private String nsUri,localName;
103
104    /**
105     * If we have the infoset representation for the payload, this field is non-null.
106     */
107    private XMLStreamBuffer infoset;
108
109    public static Message create(BindingContext context, Object jaxbObject, SOAPVersion soapVersion, MessageHeaders headers, AttachmentSet attachments) {
110        if(!context.hasSwaRef()) {
111            return new JAXBMessage(context,jaxbObject,soapVersion,headers,attachments);
112        }
113
114        // If we have swaRef, then that means we might have attachments.
115        // to comply with the packet API, we need to eagerly turn the JAXB object into infoset
116        // to correctly find out about attachments.
117
118        try {
119            MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
120
121            Marshaller m = context.createMarshaller();
122            AttachmentMarshallerImpl am = new AttachmentMarshallerImpl(attachments);
123            m.setAttachmentMarshaller(am);
124            am.cleanup();
125            m.marshal(jaxbObject,xsb.createFromXMLStreamWriter());
126
127            // any way to reuse this XMLStreamBuffer in StreamMessage?
128            return new StreamMessage(headers,attachments,xsb.readAsXMLStreamReader(),soapVersion);
129        } catch (JAXBException e) {
130            throw new WebServiceException(e);
131        } catch (XMLStreamException e) {
132            throw new WebServiceException(e);
133        }
134    }
135    /**
136     * Creates a {@link Message} backed by a JAXB bean.
137     *
138     * @param context
139     *      The JAXBContext to be used for marshalling.
140     * @param jaxbObject
141     *      The JAXB object that represents the payload. must not be null. This object
142     *      must be bound to an element (which means it either is a {@link JAXBElement} or
143     *      an instanceof a class with {@link XmlRootElement}).
144     * @param soapVersion
145     *      The SOAP version of the message. Must not be null.
146     */
147    public static Message create(BindingContext context, Object jaxbObject, SOAPVersion soapVersion) {
148        return create(context,jaxbObject,soapVersion,null,null);
149    }
150    /** @deprecated */
151    public static Message create(JAXBContext context, Object jaxbObject, SOAPVersion soapVersion) {
152        return create(BindingContextFactory.create(context),jaxbObject,soapVersion,null,null);
153    }
154
155    /**
156     * @deprecated
157     * For use when creating a Dispatch object with an unknown JAXB implementation
158     * for he JAXBContext parameter.
159     *
160     */
161    public static Message createRaw(JAXBContext context, Object jaxbObject, SOAPVersion soapVersion) {
162        return new JAXBMessage(context,jaxbObject,soapVersion,null,null);
163    }
164
165    private JAXBMessage( BindingContext context, Object jaxbObject, SOAPVersion soapVer, MessageHeaders headers, AttachmentSet attachments ) {
166        super(soapVer);
167//        this.bridge = new MarshallerBridge(context);
168        this.bridge = context.createFragmentBridge();
169        this.rawContext = null;
170        this.jaxbObject = jaxbObject;
171        this.headers = headers;
172        this.attachmentSet = attachments;
173    }
174
175    private JAXBMessage( JAXBContext rawContext, Object jaxbObject, SOAPVersion soapVer, MessageHeaders headers, AttachmentSet attachments ) {
176        super(soapVer);
177//        this.bridge = new MarshallerBridge(context);
178        this.rawContext = rawContext;
179        this.bridge = null;
180        this.jaxbObject = jaxbObject;
181        this.headers = headers;
182        this.attachmentSet = attachments;
183    }
184
185    /**
186     * Creates a {@link Message} backed by a JAXB bean.
187     *
188     * @param bridge
189     *      Specify the payload tag name and how {@code jaxbObject} is bound.
190     * @param jaxbObject
191     */
192    public static Message create(XMLBridge bridge, Object jaxbObject, SOAPVersion soapVer) {
193        if(!bridge.context().hasSwaRef()) {
194            return new JAXBMessage(bridge,jaxbObject,soapVer);
195        }
196
197        // If we have swaRef, then that means we might have attachments.
198        // to comply with the packet API, we need to eagerly turn the JAXB object into infoset
199        // to correctly find out about attachments.
200
201        try {
202            MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
203
204            AttachmentSetImpl attachments = new AttachmentSetImpl();
205            AttachmentMarshallerImpl am = new AttachmentMarshallerImpl(attachments);
206            bridge.marshal(jaxbObject,xsb.createFromXMLStreamWriter(), am);
207            am.cleanup();
208
209            // any way to reuse this XMLStreamBuffer in StreamMessage?
210            return new StreamMessage(null,attachments,xsb.readAsXMLStreamReader(),soapVer);
211        } catch (JAXBException e) {
212            throw new WebServiceException(e);
213        } catch (XMLStreamException e) {
214            throw new WebServiceException(e);
215        }
216    }
217
218    private JAXBMessage(XMLBridge bridge, Object jaxbObject, SOAPVersion soapVer) {
219        super(soapVer);
220        // TODO: think about a better way to handle BridgeContext
221        this.bridge = bridge;
222        this.rawContext = null;
223        this.jaxbObject = jaxbObject;
224        QName tagName = bridge.getTypeInfo().tagName;
225        this.nsUri = tagName.getNamespaceURI();
226        this.localName = tagName.getLocalPart();
227        this.attachmentSet = new AttachmentSetImpl();
228    }
229
230    /**
231     * Copy constructor.
232     */
233    public JAXBMessage(JAXBMessage that) {
234        super(that);
235        this.headers = that.headers;
236        if(this.headers!=null)
237            this.headers = new HeaderList(this.headers);
238        this.attachmentSet = that.attachmentSet;
239
240        this.jaxbObject = that.jaxbObject;
241        this.bridge = that.bridge;
242        this.rawContext = that.rawContext;
243        this.copyFrom(that);
244    }
245
246    @Override
247    public boolean hasHeaders() {
248        return headers!=null && headers.hasHeaders();
249    }
250
251    @Override
252    public MessageHeaders getHeaders() {
253        if(headers==null)
254            headers = new HeaderList(getSOAPVersion());
255        return headers;
256    }
257
258    @Override
259    public String getPayloadLocalPart() {
260        if(localName==null)
261            sniff();
262        return localName;
263    }
264
265    @Override
266    public String getPayloadNamespaceURI() {
267        if(nsUri==null)
268            sniff();
269        return nsUri;
270    }
271
272    @Override
273    public boolean hasPayload() {
274        return true;
275    }
276
277    /**
278     * Obtains the tag name of the root element.
279     */
280    private void sniff() {
281        RootElementSniffer sniffer = new RootElementSniffer(false);
282        try {
283                if (rawContext != null) {
284                        Marshaller m = rawContext.createMarshaller();
285                        m.setProperty("jaxb.fragment", Boolean.TRUE);
286                        m.marshal(jaxbObject,sniffer);
287                } else
288                        bridge.marshal(jaxbObject,sniffer,null);
289        } catch (JAXBException e) {
290            // if it's due to us aborting the processing after the first element,
291            // we can safely ignore this exception.
292            //
293            // if it's due to error in the object, the same error will be reported
294            // when the readHeader() method is used, so we don't have to report
295            // an error right now.
296            nsUri = sniffer.getNsUri();
297            localName = sniffer.getLocalName();
298        }
299    }
300
301    @Override
302    public Source readPayloadAsSource() {
303        return new JAXBBridgeSource(bridge,jaxbObject);
304    }
305
306    @Override
307    public <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException {
308        JAXBResult out = new JAXBResult(unmarshaller);
309        // since the bridge only produces fragments, we need to fire start/end document.
310        try {
311            out.getHandler().startDocument();
312            if (rawContext != null) {
313                Marshaller m = rawContext.createMarshaller();
314                m.setProperty("jaxb.fragment", Boolean.TRUE);
315                m.marshal(jaxbObject,out);
316            } else
317                bridge.marshal(jaxbObject,out);
318            out.getHandler().endDocument();
319        } catch (SAXException e) {
320            throw new JAXBException(e);
321        }
322        return (T)out.getResult();
323    }
324
325    @Override
326    public XMLStreamReader readPayload() throws XMLStreamException {
327       try {
328            if(infoset==null) {
329                                if (rawContext != null) {
330                        XMLStreamBufferResult sbr = new XMLStreamBufferResult();
331                                        Marshaller m = rawContext.createMarshaller();
332                                        m.setProperty("jaxb.fragment", Boolean.TRUE);
333                                        m.marshal(jaxbObject, sbr);
334                        infoset = sbr.getXMLStreamBuffer();
335                                } else {
336                                    MutableXMLStreamBuffer buffer = new MutableXMLStreamBuffer();
337                                    writePayloadTo(buffer.createFromXMLStreamWriter());
338                                    infoset = buffer;
339                                }
340            }
341            XMLStreamReader reader = infoset.readAsXMLStreamReader();
342            if(reader.getEventType()== START_DOCUMENT)
343                XMLStreamReaderUtil.nextElementContent(reader);
344            return reader;
345        } catch (JAXBException e) {
346           // bug 6449684, spec 4.3.4
347           throw new WebServiceException(e);
348        }
349    }
350
351    /**
352     * Writes the payload as SAX events.
353     */
354    @Override
355    protected void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
356        try {
357            if(fragment)
358                contentHandler = new FragmentContentHandler(contentHandler);
359            AttachmentMarshallerImpl am = new AttachmentMarshallerImpl(attachmentSet);
360            if (rawContext != null) {
361                Marshaller m = rawContext.createMarshaller();
362                m.setProperty("jaxb.fragment", Boolean.TRUE);
363                m.setAttachmentMarshaller(am);
364                m.marshal(jaxbObject,contentHandler);
365            } else
366                bridge.marshal(jaxbObject,contentHandler, am);
367            am.cleanup();
368        } catch (JAXBException e) {
369            // this is really more helpful but spec compliance
370            // errorHandler.fatalError(new SAXParseException(e.getMessage(),NULL_LOCATOR,e));
371            // bug 6449684, spec 4.3.4
372            throw new WebServiceException(e.getMessage(),e);
373        }
374    }
375
376    @Override
377    public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException {
378        try {
379            // MtomCodec sets its own AttachmentMarshaller
380            AttachmentMarshaller am = (sw instanceof MtomStreamWriter)
381                    ? ((MtomStreamWriter)sw).getAttachmentMarshaller()
382                    : new AttachmentMarshallerImpl(attachmentSet);
383
384            // Get the encoding of the writer
385            String encoding = XMLStreamWriterUtil.getEncoding(sw);
386
387            // Get output stream and use JAXB UTF-8 writer
388            OutputStream os = bridge.supportOutputStream() ? XMLStreamWriterUtil.getOutputStream(sw) : null;
389            if (rawContext != null) {
390                Marshaller m = rawContext.createMarshaller();
391                m.setProperty("jaxb.fragment", Boolean.TRUE);
392                m.setAttachmentMarshaller(am);
393                if (os != null) {
394                    m.marshal(jaxbObject, os);
395                } else {
396                    m.marshal(jaxbObject, sw);
397                }
398            } else {
399                if (os != null && encoding != null && encoding.equalsIgnoreCase(SOAPBindingCodec.UTF8_ENCODING)) {
400                    bridge.marshal(jaxbObject, os, sw.getNamespaceContext(), am);
401                } else {
402                    bridge.marshal(jaxbObject, sw, am);
403                }
404            }
405            //cleanup() is not needed since JAXB doesn't keep ref to AttachmentMarshaller
406            //am.cleanup();
407        } catch (JAXBException e) {
408            // bug 6449684, spec 4.3.4
409            throw new WebServiceException(e);
410        }
411    }
412
413    @Override
414    public Message copy() {
415        return new JAXBMessage(this).copyFrom(this);
416    }
417
418    public XMLStreamReader readEnvelope() {
419        int base = soapVersion.ordinal()*3;
420        this.envelopeTag = DEFAULT_TAGS.get(base);
421        this.bodyTag = DEFAULT_TAGS.get(base+2);
422        List<XMLStreamReader> hReaders = new java.util.ArrayList<XMLStreamReader>();
423        ElemInfo envElem =  new ElemInfo(envelopeTag, null);
424        ElemInfo bdyElem =  new ElemInfo(bodyTag, envElem);
425        for (Header h : getHeaders().asList()) {
426            try {
427                hReaders.add(h.readHeader());
428            } catch (XMLStreamException e) {
429                throw new RuntimeException(e);
430            }
431        }
432        XMLStreamReader soapHeader = null;
433        if(hReaders.size()>0) {
434            headerTag = DEFAULT_TAGS.get(base+1);
435            ElemInfo hdrElem = new ElemInfo(headerTag, envElem);
436            soapHeader = new XMLReaderComposite(hdrElem, hReaders.toArray(new XMLStreamReader[hReaders.size()]));
437        }
438        try {
439            XMLStreamReader payload= readPayload();
440            XMLStreamReader soapBody = new XMLReaderComposite(bdyElem, new XMLStreamReader[]{payload});
441            XMLStreamReader[] soapContent = (soapHeader != null) ? new XMLStreamReader[]{soapHeader, soapBody} : new XMLStreamReader[]{soapBody};
442            return new XMLReaderComposite(envElem, soapContent);
443        } catch (XMLStreamException e) {
444            throw new RuntimeException(e);
445        }
446    }
447
448    public boolean isPayloadStreamReader() { return false; }
449
450    public QName getPayloadQName() {
451        return new QName(getPayloadNamespaceURI(), getPayloadLocalPart());
452    }
453
454    public XMLStreamReader readToBodyStarTag() {
455        int base = soapVersion.ordinal()*3;
456        this.envelopeTag = DEFAULT_TAGS.get(base);
457        this.bodyTag = DEFAULT_TAGS.get(base+2);
458        List<XMLStreamReader> hReaders = new java.util.ArrayList<XMLStreamReader>();
459        ElemInfo envElem =  new ElemInfo(envelopeTag, null);
460        ElemInfo bdyElem =  new ElemInfo(bodyTag, envElem);
461        for (Header h : getHeaders().asList()) {
462            try {
463                hReaders.add(h.readHeader());
464            } catch (XMLStreamException e) {
465                throw new RuntimeException(e);
466            }
467        }
468        XMLStreamReader soapHeader = null;
469        if(hReaders.size()>0) {
470            headerTag = DEFAULT_TAGS.get(base+1);
471            ElemInfo hdrElem = new ElemInfo(headerTag, envElem);
472            soapHeader = new XMLReaderComposite(hdrElem, hReaders.toArray(new XMLStreamReader[hReaders.size()]));
473        }
474        XMLStreamReader soapBody = new XMLReaderComposite(bdyElem, new XMLStreamReader[]{});
475        XMLStreamReader[] soapContent = (soapHeader != null) ? new XMLStreamReader[]{soapHeader, soapBody} : new XMLStreamReader[]{soapBody};
476        return new XMLReaderComposite(envElem, soapContent);
477    }
478}
479