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.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.ws.streaming.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 <tt>jaxbObject</tt> 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 } 244 245 @Override 246 public boolean hasHeaders() { 247 return headers!=null && headers.hasHeaders(); 248 } 249 250 @Override 251 public MessageHeaders getHeaders() { 252 if(headers==null) 253 headers = new HeaderList(getSOAPVersion()); 254 return headers; 255 } 256 257 @Override 258 public String getPayloadLocalPart() { 259 if(localName==null) 260 sniff(); 261 return localName; 262 } 263 264 @Override 265 public String getPayloadNamespaceURI() { 266 if(nsUri==null) 267 sniff(); 268 return nsUri; 269 } 270 271 @Override 272 public boolean hasPayload() { 273 return true; 274 } 275 276 /** 277 * Obtains the tag name of the root element. 278 */ 279 private void sniff() { 280 RootElementSniffer sniffer = new RootElementSniffer(false); 281 try { 282 if (rawContext != null) { 283 Marshaller m = rawContext.createMarshaller(); 284 m.setProperty("jaxb.fragment", Boolean.TRUE); 285 m.marshal(jaxbObject,sniffer); 286 } else 287 bridge.marshal(jaxbObject,sniffer,null); 288 } catch (JAXBException e) { 289 // if it's due to us aborting the processing after the first element, 290 // we can safely ignore this exception. 291 // 292 // if it's due to error in the object, the same error will be reported 293 // when the readHeader() method is used, so we don't have to report 294 // an error right now. 295 nsUri = sniffer.getNsUri(); 296 localName = sniffer.getLocalName(); 297 } 298 } 299 300 @Override 301 public Source readPayloadAsSource() { 302 return new JAXBBridgeSource(bridge,jaxbObject); 303 } 304 305 @Override 306 public <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException { 307 JAXBResult out = new JAXBResult(unmarshaller); 308 // since the bridge only produces fragments, we need to fire start/end document. 309 try { 310 out.getHandler().startDocument(); 311 if (rawContext != null) { 312 Marshaller m = rawContext.createMarshaller(); 313 m.setProperty("jaxb.fragment", Boolean.TRUE); 314 m.marshal(jaxbObject,out); 315 } else 316 bridge.marshal(jaxbObject,out); 317 out.getHandler().endDocument(); 318 } catch (SAXException e) { 319 throw new JAXBException(e); 320 } 321 return (T)out.getResult(); 322 } 323 324 @Override 325 public XMLStreamReader readPayload() throws XMLStreamException { 326 try { 327 if(infoset==null) { 328 if (rawContext != null) { 329 XMLStreamBufferResult sbr = new XMLStreamBufferResult(); 330 Marshaller m = rawContext.createMarshaller(); 331 m.setProperty("jaxb.fragment", Boolean.TRUE); 332 m.marshal(jaxbObject, sbr); 333 infoset = sbr.getXMLStreamBuffer(); 334 } else { 335 MutableXMLStreamBuffer buffer = new MutableXMLStreamBuffer(); 336 writePayloadTo(buffer.createFromXMLStreamWriter()); 337 infoset = buffer; 338 } 339 } 340 XMLStreamReader reader = infoset.readAsXMLStreamReader(); 341 if(reader.getEventType()== START_DOCUMENT) 342 XMLStreamReaderUtil.nextElementContent(reader); 343 return reader; 344 } catch (JAXBException e) { 345 // bug 6449684, spec 4.3.4 346 throw new WebServiceException(e); 347 } 348 } 349 350 /** 351 * Writes the payload as SAX events. 352 */ 353 @Override 354 protected void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException { 355 try { 356 if(fragment) 357 contentHandler = new FragmentContentHandler(contentHandler); 358 AttachmentMarshallerImpl am = new AttachmentMarshallerImpl(attachmentSet); 359 if (rawContext != null) { 360 Marshaller m = rawContext.createMarshaller(); 361 m.setProperty("jaxb.fragment", Boolean.TRUE); 362 m.setAttachmentMarshaller(am); 363 m.marshal(jaxbObject,contentHandler); 364 } else 365 bridge.marshal(jaxbObject,contentHandler, am); 366 am.cleanup(); 367 } catch (JAXBException e) { 368 // this is really more helpful but spec compliance 369 // errorHandler.fatalError(new SAXParseException(e.getMessage(),NULL_LOCATOR,e)); 370 // bug 6449684, spec 4.3.4 371 throw new WebServiceException(e.getMessage(),e); 372 } 373 } 374 375 @Override 376 public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException { 377 try { 378 // MtomCodec sets its own AttachmentMarshaller 379 AttachmentMarshaller am = (sw instanceof MtomStreamWriter) 380 ? ((MtomStreamWriter)sw).getAttachmentMarshaller() 381 : new AttachmentMarshallerImpl(attachmentSet); 382 383 // Get the encoding of the writer 384 String encoding = XMLStreamWriterUtil.getEncoding(sw); 385 386 // Get output stream and use JAXB UTF-8 writer 387 OutputStream os = bridge.supportOutputStream() ? XMLStreamWriterUtil.getOutputStream(sw) : null; 388 if (rawContext != null) { 389 Marshaller m = rawContext.createMarshaller(); 390 m.setProperty("jaxb.fragment", Boolean.TRUE); 391 m.setAttachmentMarshaller(am); 392 if (os != null) { 393 m.marshal(jaxbObject, os); 394 } else { 395 m.marshal(jaxbObject, sw); 396 } 397 } else { 398 if (os != null && encoding != null && encoding.equalsIgnoreCase(SOAPBindingCodec.UTF8_ENCODING)) { 399 bridge.marshal(jaxbObject, os, sw.getNamespaceContext(), am); 400 } else { 401 bridge.marshal(jaxbObject, sw, am); 402 } 403 } 404 //cleanup() is not needed since JAXB doesn't keep ref to AttachmentMarshaller 405 //am.cleanup(); 406 } catch (JAXBException e) { 407 // bug 6449684, spec 4.3.4 408 throw new WebServiceException(e); 409 } 410 } 411 412 @Override 413 public Message copy() { 414 return new JAXBMessage(this); 415 } 416 417 public XMLStreamReader readEnvelope() { 418 int base = soapVersion.ordinal()*3; 419 this.envelopeTag = DEFAULT_TAGS.get(base); 420 this.bodyTag = DEFAULT_TAGS.get(base+2); 421 List<XMLStreamReader> hReaders = new java.util.ArrayList<XMLStreamReader>(); 422 ElemInfo envElem = new ElemInfo(envelopeTag, null); 423 ElemInfo bdyElem = new ElemInfo(bodyTag, envElem); 424 for (Header h : getHeaders().asList()) { 425 try { 426 hReaders.add(h.readHeader()); 427 } catch (XMLStreamException e) { 428 throw new RuntimeException(e); 429 } 430 } 431 XMLStreamReader soapHeader = null; 432 if(hReaders.size()>0) { 433 headerTag = DEFAULT_TAGS.get(base+1); 434 ElemInfo hdrElem = new ElemInfo(headerTag, envElem); 435 soapHeader = new XMLReaderComposite(hdrElem, hReaders.toArray(new XMLStreamReader[hReaders.size()])); 436 } 437 try { 438 XMLStreamReader payload= readPayload(); 439 XMLStreamReader soapBody = new XMLReaderComposite(bdyElem, new XMLStreamReader[]{payload}); 440 XMLStreamReader[] soapContent = (soapHeader != null) ? new XMLStreamReader[]{soapHeader, soapBody} : new XMLStreamReader[]{soapBody}; 441 return new XMLReaderComposite(envElem, soapContent); 442 } catch (XMLStreamException e) { 443 throw new RuntimeException(e); 444 } 445 } 446 }