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 }