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 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.Header; 35 import com.sun.xml.internal.ws.api.message.HeaderList; 36 import com.sun.xml.internal.ws.api.message.Message; 37 import com.sun.xml.internal.ws.api.message.MessageHeaders; 38 import com.sun.xml.internal.ws.api.message.StreamingSOAP; 39 import com.sun.xml.internal.ws.encoding.SOAPBindingCodec; 40 import com.sun.xml.internal.ws.message.AbstractMessageImpl; 41 import com.sun.xml.internal.ws.message.AttachmentSetImpl; 42 import com.sun.xml.internal.ws.message.RootElementSniffer; 43 import com.sun.xml.internal.ws.message.stream.StreamMessage; 44 import com.sun.xml.internal.ws.spi.db.BindingContext; 45 import com.sun.xml.internal.ws.spi.db.BindingContextFactory; 46 import com.sun.xml.internal.ws.spi.db.XMLBridge; 47 import com.sun.xml.internal.ws.streaming.XMLStreamWriterUtil; 48 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil; 49 import com.sun.xml.internal.org.jvnet.staxex.util.MtomStreamWriter; 50 import com.sun.xml.internal.ws.util.xml.XMLReaderComposite; 51 import com.sun.xml.internal.ws.util.xml.XMLReaderComposite.ElemInfo; 52 53 import org.xml.sax.ContentHandler; 54 import org.xml.sax.ErrorHandler; 55 import org.xml.sax.SAXException; 56 57 import javax.xml.bind.JAXBContext; 58 import javax.xml.bind.JAXBElement; 59 import javax.xml.bind.JAXBException; 60 import javax.xml.bind.Marshaller; 61 import javax.xml.bind.Unmarshaller; 62 import javax.xml.bind.attachment.AttachmentMarshaller; 63 import javax.xml.bind.annotation.XmlRootElement; 64 import javax.xml.bind.util.JAXBResult; 65 import javax.xml.namespace.QName; 66 import javax.xml.stream.XMLStreamException; 67 import javax.xml.stream.XMLStreamReader; 68 import javax.xml.stream.XMLStreamWriter; 69 import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT; 70 import javax.xml.transform.Source; 71 import javax.xml.ws.WebServiceException; 72 import java.io.OutputStream; 73 import java.util.List; 74 75 /** 76 * {@link Message} backed by a JAXB bean. 77 * 78 * @author Kohsuke Kawaguchi 79 */ 80 public 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 }