1 /*
   2  * Copyright (c) 1997, 2013, 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 
  26 package com.sun.xml.internal.ws.message.jaxb;
  27 
  28 import com.sun.istack.internal.FragmentContentHandler;
  29 import com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer;
  30 import com.sun.xml.internal.stream.buffer.XMLStreamBuffer;
  31 import com.sun.xml.internal.stream.buffer.XMLStreamBufferResult;
  32 import com.sun.xml.internal.ws.api.SOAPVersion;
  33 import com.sun.xml.internal.ws.api.message.AttachmentSet;
  34 import com.sun.xml.internal.ws.api.message.HeaderList;
  35 import com.sun.xml.internal.ws.api.message.Message;
  36 import com.sun.xml.internal.ws.api.message.MessageHeaders;
  37 import com.sun.xml.internal.ws.encoding.SOAPBindingCodec;
  38 import com.sun.xml.internal.ws.message.AbstractMessageImpl;
  39 import com.sun.xml.internal.ws.message.AttachmentSetImpl;
  40 import com.sun.xml.internal.ws.message.RootElementSniffer;
  41 import com.sun.xml.internal.ws.message.stream.StreamMessage;
  42 import com.sun.xml.internal.ws.spi.db.BindingContext;
  43 import com.sun.xml.internal.ws.spi.db.BindingContextFactory;
  44 import com.sun.xml.internal.ws.spi.db.XMLBridge;
  45 import com.sun.xml.internal.ws.streaming.XMLStreamWriterUtil;
  46 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
  47 import com.sun.xml.internal.ws.streaming.MtomStreamWriter;
  48 import org.xml.sax.ContentHandler;
  49 import org.xml.sax.ErrorHandler;
  50 import org.xml.sax.SAXException;
  51 
  52 import javax.xml.bind.JAXBContext;
  53 import javax.xml.bind.JAXBElement;
  54 import javax.xml.bind.JAXBException;
  55 import javax.xml.bind.Marshaller;
  56 import javax.xml.bind.Unmarshaller;
  57 import javax.xml.bind.attachment.AttachmentMarshaller;
  58 import javax.xml.bind.annotation.XmlRootElement;
  59 import javax.xml.bind.util.JAXBResult;
  60 import javax.xml.namespace.QName;
  61 import javax.xml.stream.XMLStreamException;
  62 import javax.xml.stream.XMLStreamReader;
  63 import javax.xml.stream.XMLStreamWriter;
  64 import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
  65 import javax.xml.transform.Source;
  66 import javax.xml.ws.WebServiceException;
  67 import java.io.OutputStream;
  68 
  69 /**
  70  * {@link Message} backed by a JAXB bean.
  71  *
  72  * @author Kohsuke Kawaguchi
  73  */
  74 public final class JAXBMessage extends AbstractMessageImpl {
  75     private MessageHeaders headers;
  76 
  77     /**
  78      * The JAXB object that represents the payload.
  79      */
  80     private final Object jaxbObject;
  81 
  82     private final XMLBridge bridge;
  83 
  84     /**
  85      * For the use case of a user-supplied JAXB context that is not
  86      * a known JAXB type, as when creating a Disaptch object with a
  87      * JAXB object parameter, we will marshal and unmarshal directly with
  88      * the context object, as there is no Bond available.  In this case,
  89      * swaRef is not supported.
  90      */
  91     private final JAXBContext rawContext;
  92 
  93     /**
  94      * Lazily sniffed payload element name
  95      */
  96     private String nsUri,localName;
  97 
  98     /**
  99      * If we have the infoset representation for the payload, this field is non-null.
 100      */
 101     private XMLStreamBuffer infoset;
 102 
 103     public static Message create(BindingContext context, Object jaxbObject, SOAPVersion soapVersion, MessageHeaders headers, AttachmentSet attachments) {
 104         if(!context.hasSwaRef()) {
 105             return new JAXBMessage(context,jaxbObject,soapVersion,headers,attachments);
 106         }
 107 
 108         // If we have swaRef, then that means we might have attachments.
 109         // to comply with the packet API, we need to eagerly turn the JAXB object into infoset
 110         // to correctly find out about attachments.
 111 
 112         try {
 113             MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
 114 
 115             Marshaller m = context.createMarshaller();
 116             AttachmentMarshallerImpl am = new AttachmentMarshallerImpl(attachments);
 117             m.setAttachmentMarshaller(am);
 118             am.cleanup();
 119             m.marshal(jaxbObject,xsb.createFromXMLStreamWriter());
 120 
 121             // any way to reuse this XMLStreamBuffer in StreamMessage?
 122             return new StreamMessage(headers,attachments,xsb.readAsXMLStreamReader(),soapVersion);
 123         } catch (JAXBException e) {
 124             throw new WebServiceException(e);
 125         } catch (XMLStreamException e) {
 126             throw new WebServiceException(e);
 127         }
 128     }
 129     /**
 130      * Creates a {@link Message} backed by a JAXB bean.
 131      *
 132      * @param context
 133      *      The JAXBContext to be used for marshalling.
 134      * @param jaxbObject
 135      *      The JAXB object that represents the payload. must not be null. This object
 136      *      must be bound to an element (which means it either is a {@link JAXBElement} or
 137      *      an instanceof a class with {@link XmlRootElement}).
 138      * @param soapVersion
 139      *      The SOAP version of the message. Must not be null.
 140      */
 141     public static Message create(BindingContext context, Object jaxbObject, SOAPVersion soapVersion) {
 142         return create(context,jaxbObject,soapVersion,null,null);
 143     }
 144     /** @deprecated */
 145     public static Message create(JAXBContext context, Object jaxbObject, SOAPVersion soapVersion) {
 146         return create(BindingContextFactory.create(context),jaxbObject,soapVersion,null,null);
 147     }
 148 
 149     /**
 150      * @deprecated
 151      * For use when creating a Dispatch object with an unknown JAXB implementation
 152      * for he JAXBContext parameter.
 153      *
 154      */
 155     public static Message createRaw(JAXBContext context, Object jaxbObject, SOAPVersion soapVersion) {
 156         return new JAXBMessage(context,jaxbObject,soapVersion,null,null);
 157     }
 158 
 159     private JAXBMessage( BindingContext context, Object jaxbObject, SOAPVersion soapVer, MessageHeaders headers, AttachmentSet attachments ) {
 160         super(soapVer);
 161 //        this.bridge = new MarshallerBridge(context);
 162         this.bridge = context.createFragmentBridge();
 163         this.rawContext = null;
 164         this.jaxbObject = jaxbObject;
 165         this.headers = headers;
 166         this.attachmentSet = attachments;
 167     }
 168 
 169     private JAXBMessage( JAXBContext rawContext, Object jaxbObject, SOAPVersion soapVer, MessageHeaders headers, AttachmentSet attachments ) {
 170         super(soapVer);
 171 //        this.bridge = new MarshallerBridge(context);
 172         this.rawContext = rawContext;
 173         this.bridge = null;
 174         this.jaxbObject = jaxbObject;
 175         this.headers = headers;
 176         this.attachmentSet = attachments;
 177     }
 178 
 179     /**
 180      * Creates a {@link Message} backed by a JAXB bean.
 181      *
 182      * @param bridge
 183      *      Specify the payload tag name and how <tt>jaxbObject</tt> is bound.
 184      * @param jaxbObject
 185      */
 186     public static Message create(XMLBridge bridge, Object jaxbObject, SOAPVersion soapVer) {
 187         if(!bridge.context().hasSwaRef()) {
 188             return new JAXBMessage(bridge,jaxbObject,soapVer);
 189         }
 190 
 191         // If we have swaRef, then that means we might have attachments.
 192         // to comply with the packet API, we need to eagerly turn the JAXB object into infoset
 193         // to correctly find out about attachments.
 194 
 195         try {
 196             MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
 197 
 198             AttachmentSetImpl attachments = new AttachmentSetImpl();
 199             AttachmentMarshallerImpl am = new AttachmentMarshallerImpl(attachments);
 200             bridge.marshal(jaxbObject,xsb.createFromXMLStreamWriter(), am);
 201             am.cleanup();
 202 
 203             // any way to reuse this XMLStreamBuffer in StreamMessage?
 204             return new StreamMessage(null,attachments,xsb.readAsXMLStreamReader(),soapVer);
 205         } catch (JAXBException e) {
 206             throw new WebServiceException(e);
 207         } catch (XMLStreamException e) {
 208             throw new WebServiceException(e);
 209         }
 210     }
 211 
 212     private JAXBMessage(XMLBridge bridge, Object jaxbObject, SOAPVersion soapVer) {
 213         super(soapVer);
 214         // TODO: think about a better way to handle BridgeContext
 215         this.bridge = bridge;
 216         this.rawContext = null;
 217         this.jaxbObject = jaxbObject;
 218         QName tagName = bridge.getTypeInfo().tagName;
 219         this.nsUri = tagName.getNamespaceURI();
 220         this.localName = tagName.getLocalPart();
 221         this.attachmentSet = new AttachmentSetImpl();
 222     }
 223 
 224     /**
 225      * Copy constructor.
 226      */
 227     public JAXBMessage(JAXBMessage that) {
 228         super(that);
 229         this.headers = that.headers;
 230         if(this.headers!=null)
 231             this.headers = new HeaderList(this.headers);
 232         this.attachmentSet = that.attachmentSet;
 233 
 234         this.jaxbObject = that.jaxbObject;
 235         this.bridge = that.bridge;
 236         this.rawContext = that.rawContext;
 237     }
 238 
 239     @Override
 240     public boolean hasHeaders() {
 241         return headers!=null && headers.hasHeaders();
 242     }
 243 
 244     @Override
 245     public MessageHeaders getHeaders() {
 246         if(headers==null)
 247             headers = new HeaderList(getSOAPVersion());
 248         return headers;
 249     }
 250 
 251     @Override
 252     public String getPayloadLocalPart() {
 253         if(localName==null)
 254             sniff();
 255         return localName;
 256     }
 257 
 258     @Override
 259     public String getPayloadNamespaceURI() {
 260         if(nsUri==null)
 261             sniff();
 262         return nsUri;
 263     }
 264 
 265     @Override
 266     public boolean hasPayload() {
 267         return true;
 268     }
 269 
 270     /**
 271      * Obtains the tag name of the root element.
 272      */
 273     private void sniff() {
 274         RootElementSniffer sniffer = new RootElementSniffer(false);
 275         try {
 276                 if (rawContext != null) {
 277                         Marshaller m = rawContext.createMarshaller();
 278                         m.setProperty("jaxb.fragment", Boolean.TRUE);
 279                         m.marshal(jaxbObject,sniffer);
 280                 } else
 281                         bridge.marshal(jaxbObject,sniffer,null);
 282         } catch (JAXBException e) {
 283             // if it's due to us aborting the processing after the first element,
 284             // we can safely ignore this exception.
 285             //
 286             // if it's due to error in the object, the same error will be reported
 287             // when the readHeader() method is used, so we don't have to report
 288             // an error right now.
 289             nsUri = sniffer.getNsUri();
 290             localName = sniffer.getLocalName();
 291         }
 292     }
 293 
 294     @Override
 295     public Source readPayloadAsSource() {
 296         return new JAXBBridgeSource(bridge,jaxbObject);
 297     }
 298 
 299     @Override
 300     public <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException {
 301         JAXBResult out = new JAXBResult(unmarshaller);
 302         // since the bridge only produces fragments, we need to fire start/end document.
 303         try {
 304             out.getHandler().startDocument();
 305             if (rawContext != null) {
 306                 Marshaller m = rawContext.createMarshaller();
 307                 m.setProperty("jaxb.fragment", Boolean.TRUE);
 308                 m.marshal(jaxbObject,out);
 309             } else
 310                 bridge.marshal(jaxbObject,out);
 311             out.getHandler().endDocument();
 312         } catch (SAXException e) {
 313             throw new JAXBException(e);
 314         }
 315         return (T)out.getResult();
 316     }
 317 
 318     @Override
 319     public XMLStreamReader readPayload() throws XMLStreamException {
 320        try {
 321             if(infoset==null) {
 322                 XMLStreamBufferResult sbr = new XMLStreamBufferResult();
 323                                 if (rawContext != null) {
 324                                         Marshaller m = rawContext.createMarshaller();
 325                                         m.setProperty("jaxb.fragment", Boolean.TRUE);
 326                                         m.marshal(jaxbObject, sbr);
 327                                 } else
 328                                         bridge.marshal(jaxbObject, sbr);
 329                 infoset = sbr.getXMLStreamBuffer();
 330             }
 331             XMLStreamReader reader = infoset.readAsXMLStreamReader();
 332             if(reader.getEventType()== START_DOCUMENT)
 333                 XMLStreamReaderUtil.nextElementContent(reader);
 334             return reader;
 335         } catch (JAXBException e) {
 336            // bug 6449684, spec 4.3.4
 337            throw new WebServiceException(e);
 338         }
 339     }
 340 
 341     /**
 342      * Writes the payload as SAX events.
 343      */
 344     @Override
 345     protected void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
 346         try {
 347             if(fragment)
 348                 contentHandler = new FragmentContentHandler(contentHandler);
 349             AttachmentMarshallerImpl am = new AttachmentMarshallerImpl(attachmentSet);
 350             if (rawContext != null) {
 351                 Marshaller m = rawContext.createMarshaller();
 352                 m.setProperty("jaxb.fragment", Boolean.TRUE);
 353                 m.setAttachmentMarshaller(am);
 354                 m.marshal(jaxbObject,contentHandler);
 355             } else
 356                 bridge.marshal(jaxbObject,contentHandler, am);
 357             am.cleanup();
 358         } catch (JAXBException e) {
 359             // this is really more helpful but spec compliance
 360             // errorHandler.fatalError(new SAXParseException(e.getMessage(),NULL_LOCATOR,e));
 361             // bug 6449684, spec 4.3.4
 362             throw new WebServiceException(e.getMessage(),e);
 363         }
 364     }
 365 
 366     @Override
 367     public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException {
 368         try {
 369             // MtomCodec sets its own AttachmentMarshaller
 370             AttachmentMarshaller am = (sw instanceof MtomStreamWriter)
 371                     ? ((MtomStreamWriter)sw).getAttachmentMarshaller()
 372                     : new AttachmentMarshallerImpl(attachmentSet);
 373 
 374             // Get the encoding of the writer
 375             String encoding = XMLStreamWriterUtil.getEncoding(sw);
 376 
 377             // Get output stream and use JAXB UTF-8 writer
 378             OutputStream os = bridge.supportOutputStream() ? XMLStreamWriterUtil.getOutputStream(sw) : null;
 379                         if (rawContext != null) {
 380                                 Marshaller m = rawContext.createMarshaller();
 381                                 m.setProperty("jaxb.fragment", Boolean.TRUE);
 382                                 m.setAttachmentMarshaller(am);
 383                                 if (os != null)
 384                                         m.marshal(jaxbObject, os);
 385                                 else
 386                                         m.marshal(jaxbObject, sw);
 387                         } else {
 388                                 if (os != null  && encoding != null && encoding.equalsIgnoreCase(SOAPBindingCodec.UTF8_ENCODING)) {
 389                                         bridge.marshal(jaxbObject, os, sw.getNamespaceContext(), am);
 390                                 } else {
 391                                         bridge.marshal(jaxbObject, sw, am);
 392                                 }
 393                         }
 394             //cleanup() is not needed since JAXB doesn't keep ref to AttachmentMarshaller
 395             //am.cleanup();
 396         } catch (JAXBException e) {
 397             // bug 6449684, spec 4.3.4
 398             throw new WebServiceException(e);
 399         }
 400     }
 401 
 402     @Override
 403     public Message copy() {
 404         return new JAXBMessage(this);
 405     }
 406 
 407 }