001:/*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:package com.sun.xml.ws.message.stream;
037:
038:import com.sun.istack.NotNull;
039:import com.sun.istack.Nullable;
040:import com.sun.istack.XMLStreamReaderToContentHandler;
041:import com.sun.xml.bind.api.Bridge;
042:import com.sun.xml.stream.buffer.MutableXMLStreamBuffer;
043:import com.sun.xml.stream.buffer.stax.StreamReaderBufferCreator;
044:import com.sun.xml.ws.api.SOAPVersion;
045:import com.sun.xml.ws.api.message.AttachmentSet;
046:import com.sun.xml.ws.api.message.Header;
047:import com.sun.xml.ws.api.message.HeaderList;
048:import com.sun.xml.ws.api.message.Message;
049:import com.sun.xml.ws.api.streaming.XMLStreamReaderFactory;
050:import com.sun.xml.ws.encoding.TagInfoset;
051:import com.sun.xml.ws.message.AbstractMessageImpl;
052:import com.sun.xml.ws.message.AttachmentUnmarshallerImpl;
053:import com.sun.xml.ws.streaming.XMLStreamReaderUtil;
054:import com.sun.xml.ws.util.xml.DummyLocation;
055:import com.sun.xml.ws.util.xml.StAXSource;
056:import com.sun.xml.ws.util.xml.XMLStreamReaderToXMLStreamWriter;
057:import org.xml.sax.ContentHandler;
058:import org.xml.sax.ErrorHandler;
059:import org.xml.sax.SAXException;
060:import org.xml.sax.SAXParseException;
061:
062:import javax.xml.bind.JAXBException;
063:import javax.xml.bind.Unmarshaller;
064:import javax.xml.stream.Location;
065:import javax.xml.stream.XMLStreamConstants;
066:import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
067:import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
068:import javax.xml.stream.XMLStreamException;
069:import javax.xml.stream.XMLStreamReader;
070:import javax.xml.stream.XMLStreamWriter;
071:import javax.xml.transform.Source;
072:import javax.xml.ws.WebServiceException;
073:
074:/**
075: * {@link Message} implementation backed by {@link XMLStreamReader}.
076: *
077: * TODO: we need another message class that keeps {@link XMLStreamReader} that points
078: * at the start of the envelope element.
079: */
080:public final class StreamMessage extends AbstractMessageImpl {
081: /**
082: * The reader will be positioned at
083: * the first child of the SOAP body
084: */
085: private @NotNull XMLStreamReader reader;
086:
087: // lazily created
088: private @Nullable HeaderList headers;
089:
090: private final String payloadLocalName;
091:
092: private final String payloadNamespaceURI;
093:
094: /**
095: * infoset about the SOAP envelope, header, and body.
096: *
097: * <p>
098: * If the creater of this object didn't care about those,
099: * we use stock values.
100: */
101: private /*almost final*/ @NotNull TagInfoset envelopeTag,headerTag,bodyTag;
102:
103: /**
104: * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
105: * that points at the start element of the payload, and headers.
106: *
107: * <p>
108: * This method creaets a {@link Message} from a payload.
109: *
110: * @param headers
111: * if null, it means no headers. if non-null,
112: * it will be owned by this message.
113: * @param reader
114: * points at the start element/document of the payload (or the end element of the <s:Body>
115: * if there's no payload)
116: */
117: public StreamMessage(@Nullable HeaderList headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
118: super (soapVersion);
119: this .headers = headers;
120: this .attachmentSet = attachmentSet;
121: this .reader = reader;
122:
123: if(reader.getEventType()== START_DOCUMENT)
124: XMLStreamReaderUtil.nextElementContent(reader);
125:
126: //if the reader is pointing to the end element </soapenv:Body> then its empty message
127: // or no payload
128: if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
129: String body = reader.getLocalName();
130: String nsUri = reader.getNamespaceURI();
131: assert body != null;
132: assert nsUri != null;
133: //if its not soapenv:Body then throw exception, we received malformed stream
134: if(body.equals("Body") && nsUri.equals(soapVersion.nsUri)){
135: this .payloadLocalName = null;
136: this .payloadNamespaceURI = null;
137: }else{ //TODO: i18n and also we should be throwing better message that this
138: throw new WebServiceException("Malformed stream: {"+nsUri+"}"+body);
139: }
140: }else{
141: this .payloadLocalName = reader.getLocalName();
142: this .payloadNamespaceURI = reader.getNamespaceURI();
143: }
144:
145: // use the default infoset representation for headers
146: int base = soapVersion.ordinal()*3;
147: this .envelopeTag = DEFAULT_TAGS[base];
148: this .headerTag = DEFAULT_TAGS[base+1];
149: this .bodyTag = DEFAULT_TAGS[base+2];
150: }
151:
152: /**
153: * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
154: * and the complete infoset of the SOAP envelope.
155: *
156: * <p>
157: * See {@link #StreamMessage(HeaderList, AttachmentSet, XMLStreamReader, SOAPVersion)} for
158: * the description of the basic parameters.
159: *
160: * @param headerTag
161: * Null if the message didn't have a header tag.
162: *
163: */
164: public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable HeaderList headers, @NotNull TagInfoset bodyTag, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
165: this (headers,attachmentSet,reader,soapVersion);
166: assert envelopeTag!=null && bodyTag!=null;
167: this .envelopeTag = envelopeTag;
168: this .headerTag = headerTag!=null ? headerTag :
169: new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS);
170: this .bodyTag = bodyTag;
171: }
172:
173: public boolean hasHeaders() {
174: return headers!=null && !headers.isEmpty();
175: }
176:
177: public HeaderList getHeaders() {
178: if (headers == null) {
179: headers = new HeaderList();
180: }
181: return headers;
182: }
183:
184: @Override
185: public @NotNull AttachmentSet getAttachments() {
186: return attachmentSet;
187: }
188:
189: public String getPayloadLocalPart() {
190: return payloadLocalName;
191: }
192:
193: public String getPayloadNamespaceURI() {
194: return payloadNamespaceURI;
195: }
196:
197: public boolean hasPayload() {
198: return payloadLocalName!=null;
199: }
200:
201: public Source readPayloadAsSource() {
202: if(hasPayload()) {
203: assert unconsumed();
204: return new StAXSource(reader, true);
205: } else
206: return null;
207: }
208:
209: public Object readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException {
210: if(!hasPayload())
211: return null;
212: assert unconsumed();
213: // TODO: How can the unmarshaller process this as a fragment?
214: if(hasAttachments())
215: unmarshaller.setAttachmentUnmarshaller(new AttachmentUnmarshallerImpl(getAttachments()));
216: try {
217: return unmarshaller.unmarshal(reader);
218: } finally{
219: unmarshaller.setAttachmentUnmarshaller(null);
220: XMLStreamReaderUtil.readRest(reader);
221: XMLStreamReaderUtil.close(reader);
222: XMLStreamReaderFactory.recycle(reader);
223: }
224: }
225:
226: public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException {
227: if(!hasPayload())
228: return null;
229: assert unconsumed();
230: T r = bridge.unmarshal(reader,
231: hasAttachments() ? new AttachmentUnmarshallerImpl(getAttachments()) : null);
232: XMLStreamReaderUtil.readRest(reader);
233: XMLStreamReaderUtil.close(reader);
234: XMLStreamReaderFactory.recycle(reader);
235: return r;
236: }
237:
238: @Override
239: public void consume() {
240: assert unconsumed();
241: XMLStreamReaderUtil.readRest(reader);
242: XMLStreamReaderUtil.close(reader);
243: XMLStreamReaderFactory.recycle(reader);
244: }
245:
246: public XMLStreamReader readPayload() {
247: // TODO: What about access at and beyond </soap:Body>
248: assert unconsumed();
249: return this .reader;
250: }
251:
252: public void writePayloadTo(XMLStreamWriter writer)throws XMLStreamException {
253: if(payloadLocalName==null)
254: return; // no body
255: assert unconsumed();
256: XMLStreamReaderToXMLStreamWriter conv = new XMLStreamReaderToXMLStreamWriter();
257: while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
258: String name = reader.getLocalName();
259: String nsUri = reader.getNamespaceURI();
260:
261: //after previous conv.bridge() call the cursor will be at
262: //END_ELEMENT. Check if its not soapenv:Body then move to next
263: // ELEMENT
264: if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
265: if(!name.equals("Body") || !nsUri.equals(soapVersion.nsUri)){
266: XMLStreamReaderUtil.nextElementContent(reader);
267: if(reader.getEventType() == XMLStreamConstants.END_DOCUMENT)
268: break;
269: name = reader.getLocalName();
270: nsUri = reader.getNamespaceURI();
271: }
272: }
273: if(name.equals("Body") && nsUri.equals(soapVersion.nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
274: break;
275: conv.bridge(reader,writer);
276: }
277: XMLStreamReaderUtil.readRest(reader);
278: XMLStreamReaderUtil.close(reader);
279: XMLStreamReaderFactory.recycle(reader);
280: }
281:
282: public void writeTo(XMLStreamWriter sw) throws XMLStreamException{
283: writeEnvelope(sw);
284: }
285:
286: /**
287: * This method should be called when the StreamMessage is created with a payload
288: * @param writer
289: */
290: private void writeEnvelope(XMLStreamWriter writer) throws XMLStreamException {
291: writer.writeStartDocument();
292: envelopeTag.writeStart(writer);
293:
294: //write headers
295: HeaderList hl = getHeaders();
296: if(hl.size() > 0){
297: headerTag.writeStart(writer);
298: for(Header h:hl){
299: h.writeTo(writer);
300: }
301: writer.writeEndElement();
302: }
303: bodyTag.writeStart(writer);
304: if(hasPayload())
305: writePayloadTo(writer);
306: writer.writeEndElement();
307: writer.writeEndElement();
308: writer.writeEndDocument();
309: }
310:
311: public void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
312: assert unconsumed();
313: try {
314: if(payloadLocalName==null)
315: return; // no body
316:
317: XMLStreamReaderToContentHandler conv =
318: new XMLStreamReaderToContentHandler(reader,contentHandler,true,fragment);
319:
320: while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
321: String name = reader.getLocalName();
322: String nsUri = reader.getNamespaceURI();
323:
324: //after previous conv.bridge() call the cursor will be at
325: //END_ELEMENT. Check if its not soapenv:Body then move to next
326: // ELEMENT
327: if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
328: if(!name.equals("Body") || !nsUri.equals(soapVersion.nsUri)){
329: XMLStreamReaderUtil.nextElementContent(reader);
330: if(reader.getEventType() == XMLStreamConstants.END_DOCUMENT)
331: break;
332: name = reader.getLocalName();
333: nsUri = reader.getNamespaceURI();
334: }
335: }
336: if(name.equals("Body") && nsUri.equals(soapVersion.nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
337: break;
338:
339: conv.bridge();
340: }
341: XMLStreamReaderUtil.readRest(reader);
342: XMLStreamReaderUtil.close(reader);
343: XMLStreamReaderFactory.recycle(reader);
344: } catch (XMLStreamException e) {
345: Location loc = e.getLocation();
346: if(loc==null) loc = DummyLocation.INSTANCE;
347:
348: SAXParseException x = new SAXParseException(
349: e.getMessage(),loc.getPublicId(),loc.getSystemId(),loc.getLineNumber(),loc.getColumnNumber(),e);
350: errorHandler.error(x);
351: }
352: }
353:
354: public Message copy() {
355: try {
356: // copy the payload
357: XMLStreamReader clone;
358: if(hasPayload()) {
359: assert unconsumed();
360: consumedAt = null; // but we don't want to mark it as consumed
361: MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
362:
363: //the boolean value tells the first body part is written.
364: //based on this we do the right thing
365: StreamReaderBufferCreator c = new StreamReaderBufferCreator(xsb);
366: while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
367: String name = reader.getLocalName();
368: String nsUri = reader.getNamespaceURI();
369: if(name.equals("Body") && nsUri.equals(soapVersion.nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
370: break;
371: c.create(reader);
372: // Skip whitespaces in between payload and </Body> or between elements
373: if (reader.isWhiteSpace()) {
374: XMLStreamReaderUtil.nextElementContent(reader);
375: }
376: }
377: XMLStreamReaderUtil.readRest(reader);
378: XMLStreamReaderUtil.close(reader);
379: XMLStreamReaderFactory.recycle(reader);
380:
381: reader = xsb.readAsXMLStreamReader();
382: clone = xsb.readAsXMLStreamReader();
383: // advance to the start tag of the first element
384: proceedToRootElement(reader);
385: proceedToRootElement(clone);
386: } else {
387: // it's tempting to use EmptyMessageImpl, but it doesn't presere the infoset
388: // of <envelope>,<header>, and <body>, so we need to stick to StreamMessage.
389: clone = reader;
390: }
391:
392: return new StreamMessage(envelopeTag, headerTag, attachmentSet, HeaderList.copy(headers), bodyTag, clone, soapVersion);
393: } catch (XMLStreamException e) {
394: throw new WebServiceException("Failed to copy a message",e);
395: }
396: }
397:
398: private void proceedToRootElement(XMLStreamReader xsr) throws XMLStreamException {
399: assert xsr.getEventType()==START_DOCUMENT;
400: xsr.nextTag();
401: assert xsr.getEventType()==START_ELEMENT;
402: }
403:
404: public void writeTo( ContentHandler contentHandler, ErrorHandler errorHandler ) throws SAXException {
405: contentHandler.setDocumentLocator(NULL_LOCATOR);
406: contentHandler.startDocument();
407: envelopeTag.writeStart(contentHandler);
408: headerTag.writeStart(contentHandler);
409: if(hasHeaders()) {
410: HeaderList headers = getHeaders();
411: int len = headers.size();
412: for( int i=0; i<len; i++ ) {
413: // shouldn't JDK be smart enough to use array-style indexing for this foreach!?
414: headers.get(i).writeTo(contentHandler,errorHandler);
415: }
416: }
417: headerTag.writeEnd(contentHandler);
418: bodyTag.writeStart(contentHandler);
419: writePayloadTo(contentHandler,errorHandler, true);
420: bodyTag.writeEnd(contentHandler);
421: envelopeTag.writeEnd(contentHandler);
422:
423: }
424:
425: /**
426: * Used for an assertion. Returns true when the message is unconsumed,
427: * or otherwise throw an exception.
428: *
429: * <p>
430: * Calling this method also marks the stream as 'consumed'
431: */
432: private boolean unconsumed() {
433: if(payloadLocalName==null)
434: return true; // no payload. can be consumed multiple times.
435:
436: if(reader.getEventType()!=XMLStreamReader.START_ELEMENT) {
437: AssertionError error = new AssertionError("StreamMessage has been already consumed. See the nested exception for where it's consumed");
438: error.initCause(consumedAt);
439: throw error;
440: }
441: consumedAt = new Exception().fillInStackTrace();
442: return true;
443: }
444:
445: /**
446: * Used only for debugging. This records where the message was consumed.
447: */
448: private Throwable consumedAt;
449:
450: /**
451: * Default s:Envelope, s:Header, and s:Body tag infoset definitions.
452: *
453: * We need 3 for SOAP 1.1, 3 for SOAP 1.2.
454: */
455: private static final TagInfoset[] DEFAULT_TAGS;
456:
457: static {
458: DEFAULT_TAGS = new TagInfoset[6];
459: create(SOAPVersion.SOAP_11);
460: create(SOAPVersion.SOAP_12);
461: }
462:
463: private static void create(SOAPVersion v) {
464: int base = v.ordinal()*3;
465: DEFAULT_TAGS[base ] = new TagInfoset(v.nsUri,"Envelope","S",EMPTY_ATTS,"S",v.nsUri);
466: DEFAULT_TAGS[base+1] = new TagInfoset(v.nsUri,"Header","S",EMPTY_ATTS);
467: DEFAULT_TAGS[base+2] = new TagInfoset(v.nsUri,"Body","S",EMPTY_ATTS);
468: }
469:}
|