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:
037: package com.sun.xml.ws.encoding.xml;
038:
039: import com.sun.istack.NotNull;
040: import com.sun.xml.ws.api.SOAPVersion;
041: import com.sun.xml.ws.api.WSBinding;
042: import com.sun.xml.ws.api.message.*;
043: import com.sun.xml.ws.api.model.wsdl.WSDLPort;
044: import com.sun.xml.ws.api.pipe.Codec;
045: import com.sun.xml.ws.api.streaming.XMLStreamReaderFactory;
046: import com.sun.xml.ws.api.streaming.XMLStreamWriterFactory;
047: import com.sun.xml.ws.developer.StreamingAttachmentFeature;
048: import com.sun.xml.ws.encoding.MimeMultipartParser;
049: import com.sun.xml.ws.encoding.XMLHTTPBindingCodec;
050: import com.sun.xml.ws.encoding.ContentType;
051: import com.sun.xml.ws.message.AbstractMessageImpl;
052: import com.sun.xml.ws.message.EmptyMessageImpl;
053: import com.sun.xml.ws.message.MimeAttachmentSet;
054: import com.sun.xml.ws.util.xml.XMLStreamReaderToXMLStreamWriter;
055: import com.sun.xml.ws.util.ByteArrayBuffer;
056: import org.xml.sax.ContentHandler;
057: import org.xml.sax.ErrorHandler;
058: import org.xml.sax.SAXException;
059:
060: import javax.activation.DataSource;
061: import javax.xml.stream.XMLStreamException;
062: import javax.xml.stream.XMLStreamReader;
063: import javax.xml.stream.XMLStreamWriter;
064: import javax.xml.transform.Source;
065: import javax.xml.transform.stream.StreamSource;
066: import javax.xml.ws.WebServiceException;
067: import java.io.BufferedInputStream;
068: import java.io.IOException;
069: import java.io.InputStream;
070: import java.io.OutputStream;
071:
072: /**
073: *
074: * @author Jitendra Kotamraju
075: */
076: public final class XMLMessage {
077:
078: private static final int PLAIN_XML_FLAG = 1; // 00001
079: private static final int MIME_MULTIPART_FLAG = 2; // 00010
080: private static final int FI_ENCODED_FLAG = 16; // 10000
081:
082: /**
083: * Finds if the stream has some content or not
084: *
085: * @return null if there is no data
086: * else stream to be used
087: */
088: private static InputStream hasSomeData(InputStream in)
089: throws IOException {
090: if (in != null) {
091: if (in.available() < 1) {
092: if (!in.markSupported()) {
093: in = new BufferedInputStream(in);
094: }
095: in.mark(1);
096: if (in.read() != -1) {
097: in.reset();
098: } else {
099: in = null; // No data
100: }
101: }
102: }
103: return in;
104: }
105:
106: /**
107: * Construct a message given a content type and an input stream.
108: */
109: public static Message create(final String ct, InputStream in,
110: WSBinding binding) {
111: Message data;
112: try {
113: in = hasSomeData(in);
114: if (in == null) {
115: data = Messages.createEmpty(SOAPVersion.SOAP_11);
116: return data;
117: }
118:
119: if (ct != null) {
120: final ContentType contentType = new ContentType(ct);
121: final int contentTypeId = identifyContentType(contentType);
122: if ((contentTypeId & MIME_MULTIPART_FLAG) != 0) {
123: data = new XMLMultiPart(
124: ct,
125: in,
126: binding
127: .getFeature(StreamingAttachmentFeature.class));
128: } else if ((contentTypeId & PLAIN_XML_FLAG) != 0) {
129: data = Messages.createUsingPayload(
130: new StreamSource(in), SOAPVersion.SOAP_11);
131: } else {
132: data = new UnknownContent(ct, in);
133: }
134: } else {
135: data = Messages.createEmpty(SOAPVersion.SOAP_11);
136: }
137: } catch (Exception ex) {
138: throw new WebServiceException(ex);
139: }
140: return data;
141: }
142:
143: public static Message create(Source source) {
144: return (source == null) ? Messages
145: .createEmpty(SOAPVersion.SOAP_11) : Messages
146: .createUsingPayload(source, SOAPVersion.SOAP_11);
147: }
148:
149: public static Message create(DataSource ds, WSBinding binding) {
150: try {
151: return (ds == null) ? Messages
152: .createEmpty(SOAPVersion.SOAP_11) : create(ds
153: .getContentType(), ds.getInputStream(), binding);
154: } catch (IOException ioe) {
155: throw new WebServiceException(ioe);
156: }
157: }
158:
159: public static Message create(Exception e) {
160: return new FaultMessage(SOAPVersion.SOAP_11);
161: }
162:
163: /**
164: * Get the content type ID from the content type.
165: */
166: private static int getContentId(String ct) {
167: try {
168: final ContentType contentType = new ContentType(ct);
169: return identifyContentType(contentType);
170: } catch (Exception ex) {
171: throw new WebServiceException(ex);
172: }
173: }
174:
175: /**
176: * Return true if the content uses fast infoset.
177: */
178: public static boolean isFastInfoset(String ct) {
179: return (getContentId(ct) & FI_ENCODED_FLAG) != 0;
180: }
181:
182: /**
183: * Verify a contentType.
184: *
185: * @return
186: * MIME_MULTIPART_FLAG | PLAIN_XML_FLAG
187: * MIME_MULTIPART_FLAG | FI_ENCODED_FLAG;
188: * PLAIN_XML_FLAG
189: * FI_ENCODED_FLAG
190: *
191: */
192: public static int identifyContentType(ContentType contentType) {
193: String primary = contentType.getPrimaryType();
194: String sub = contentType.getSubType();
195:
196: if (primary.equalsIgnoreCase("multipart")
197: && sub.equalsIgnoreCase("related")) {
198: String type = contentType.getParameter("type");
199: if (type != null) {
200: if (isXMLType(type)) {
201: return MIME_MULTIPART_FLAG | PLAIN_XML_FLAG;
202: } else if (isFastInfosetType(type)) {
203: return MIME_MULTIPART_FLAG | FI_ENCODED_FLAG;
204: }
205: }
206: return 0;
207: } else if (isXMLType(primary, sub)) {
208: return PLAIN_XML_FLAG;
209: } else if (isFastInfosetType(primary, sub)) {
210: return FI_ENCODED_FLAG;
211: }
212: return 0;
213: }
214:
215: protected static boolean isXMLType(@NotNull
216: String primary, @NotNull
217: String sub) {
218: return (primary.equalsIgnoreCase("text") && sub
219: .equalsIgnoreCase("xml"))
220: || (primary.equalsIgnoreCase("application") && sub
221: .equalsIgnoreCase("xml"))
222: || (primary.equalsIgnoreCase("application") && sub
223: .toLowerCase().endsWith("+xml"));
224: }
225:
226: protected static boolean isXMLType(String type) {
227: String lowerType = type.toLowerCase();
228: return lowerType.startsWith("text/xml")
229: || lowerType.startsWith("application/xml")
230: || (lowerType.startsWith("application/") && (lowerType
231: .indexOf("+xml") != -1));
232: }
233:
234: protected static boolean isFastInfosetType(String primary,
235: String sub) {
236: return primary.equalsIgnoreCase("application")
237: && sub.equalsIgnoreCase("fastinfoset");
238: }
239:
240: protected static boolean isFastInfosetType(String type) {
241: return type.toLowerCase().startsWith("application/fastinfoset");
242: }
243:
244: /**
245: * Access a {@link Message} as a {@link DataSource}.
246: * <p>
247: * A {@link Message} implementation will implement this if the
248: * messages is to be access as data source.
249: * <p>
250: * TODO: consider putting as part of the API.
251: */
252: public static interface MessageDataSource {
253: /**
254: * Check if the data source has been consumed.
255: * @return true of the data source has been consumed, otherwise false.
256: */
257: boolean hasUnconsumedDataSource();
258:
259: /**
260: * Get the data source.
261: * @return the data source.
262: */
263: DataSource getDataSource();
264: }
265:
266: /**
267: * Data represented as a multi-part MIME message.
268: * <p>
269: * The root part may be an XML or an FI document. This class
270: * parses MIME message lazily.
271: */
272: public static final class XMLMultiPart extends AbstractMessageImpl
273: implements MessageDataSource {
274: private final DataSource dataSource;
275: private MimeMultipartParser mpp;
276: private final StreamingAttachmentFeature feature;
277:
278: public XMLMultiPart(final String contentType,
279: final InputStream is, StreamingAttachmentFeature feature) {
280: super (SOAPVersion.SOAP_11);
281: dataSource = createDataSource(contentType, is);
282: this .feature = feature;
283: }
284:
285: public XMLMultiPart(DataSource dataSource,
286: StreamingAttachmentFeature feature) {
287: super (SOAPVersion.SOAP_11);
288: this .dataSource = dataSource;
289: this .feature = feature;
290: }
291:
292: public DataSource getDataSource() {
293: assert dataSource != null;
294: return dataSource;
295: }
296:
297: private void convertDataSourceToMessage() {
298: if (mpp == null) {
299: try {
300: mpp = new MimeMultipartParser(dataSource
301: .getInputStream(), dataSource
302: .getContentType(), feature);
303: } catch (IOException ioe) {
304: throw new WebServiceException(ioe);
305: }
306: }
307: }
308:
309: @Override
310: public boolean isOneWay(@NotNull
311: WSDLPort port) {
312: return false;
313: }
314:
315: public boolean isFault() {
316: return false;
317: }
318:
319: public boolean hasHeaders() {
320: return false;
321: }
322:
323: public HeaderList getHeaders() {
324: return new HeaderList();
325: }
326:
327: @Override
328: public AttachmentSet getAttachments() {
329: convertDataSourceToMessage();
330: return new MimeAttachmentSet(mpp);
331: }
332:
333: public String getPayloadLocalPart() {
334: throw new UnsupportedOperationException();
335: }
336:
337: public String getPayloadNamespaceURI() {
338: throw new UnsupportedOperationException();
339: }
340:
341: public boolean hasPayload() {
342: return true;
343: }
344:
345: public Source readPayloadAsSource() {
346: convertDataSourceToMessage();
347: return mpp.getRootPart().asSource();
348: }
349:
350: public XMLStreamReader readPayload() throws XMLStreamException {
351: convertDataSourceToMessage();
352: return XMLStreamReaderFactory.create(null, mpp
353: .getRootPart().asInputStream(), true);
354: }
355:
356: public void writePayloadTo(XMLStreamWriter sw) {
357: XMLStreamReaderToXMLStreamWriter c = new XMLStreamReaderToXMLStreamWriter();
358: try {
359: XMLStreamReader r = readPayload();
360: c.bridge(r, sw);
361: XMLStreamReaderFactory.recycle(r);
362: } catch (Exception e) {
363: throw new RuntimeException(e);
364: }
365: }
366:
367: protected void writePayloadTo(ContentHandler contentHandler,
368: ErrorHandler errorHandler, boolean fragment) {
369: throw new UnsupportedOperationException();
370: }
371:
372: public Message copy() {
373: throw new UnsupportedOperationException();
374: }
375:
376: public boolean hasUnconsumedDataSource() {
377: return mpp == null;
378: }
379:
380: }
381:
382: private static class FaultMessage extends EmptyMessageImpl {
383:
384: public FaultMessage(SOAPVersion version) {
385: super (version);
386: }
387:
388: @Override
389: public boolean isFault() {
390: return true;
391: }
392: }
393:
394: /**
395: * Don't know about this content. It's conent-type is NOT the XML types
396: * we recognize(text/xml, application/xml, multipart/related;text/xml etc).
397: *
398: * This could be used to represent image/jpeg etc
399: */
400: public static class UnknownContent extends AbstractMessageImpl
401: implements MessageDataSource {
402: private final DataSource ds;
403: private final HeaderList headerList;
404:
405: public UnknownContent(final String ct, final InputStream in) {
406: this (createDataSource(ct, in));
407: }
408:
409: public UnknownContent(DataSource ds) {
410: super (SOAPVersion.SOAP_11);
411: this .ds = ds;
412: this .headerList = new HeaderList();
413: }
414:
415: /**
416: * Copy constructor.
417: */
418: private UnknownContent(UnknownContent that) {
419: super (that.soapVersion);
420: this .ds = that.ds;
421: this .headerList = HeaderList.copy(that.headerList);
422: }
423:
424: public boolean hasUnconsumedDataSource() {
425: return true;
426: }
427:
428: public DataSource getDataSource() {
429: assert ds != null;
430: return ds;
431: }
432:
433: protected void writePayloadTo(ContentHandler contentHandler,
434: ErrorHandler errorHandler, boolean fragment)
435: throws SAXException {
436: throw new UnsupportedOperationException();
437: }
438:
439: public boolean hasHeaders() {
440: return false;
441: }
442:
443: public boolean isFault() {
444: return false;
445: }
446:
447: public HeaderList getHeaders() {
448: return headerList;
449: }
450:
451: public String getPayloadLocalPart() {
452: throw new UnsupportedOperationException();
453: }
454:
455: public String getPayloadNamespaceURI() {
456: throw new UnsupportedOperationException();
457: }
458:
459: public boolean hasPayload() {
460: return false;
461: }
462:
463: public Source readPayloadAsSource() {
464: return null;
465: }
466:
467: public XMLStreamReader readPayload() throws XMLStreamException {
468: throw new WebServiceException(
469: "There isn't XML payload. Shouldn't come here.");
470: }
471:
472: public void writePayloadTo(XMLStreamWriter sw)
473: throws XMLStreamException {
474: // No XML. Nothing to do
475: }
476:
477: public Message copy() {
478: return new UnknownContent(this );
479: }
480:
481: }
482:
483: public static DataSource getDataSource(Message msg,
484: WSBinding binding) {
485: if (msg instanceof MessageDataSource) {
486: return ((MessageDataSource) msg).getDataSource();
487: } else {
488: AttachmentSet atts = msg.getAttachments();
489: if (atts != null && !atts.isEmpty()) {
490: final ByteArrayBuffer bos = new ByteArrayBuffer();
491: try {
492: Codec codec = new XMLHTTPBindingCodec(binding);
493: com.sun.xml.ws.api.pipe.ContentType ct = codec
494: .getStaticContentType(new Packet(msg));
495: codec.encode(new Packet(msg), bos);
496: return createDataSource(ct.getContentType(), bos
497: .newInputStream());
498: } catch (IOException ioe) {
499: throw new WebServiceException(ioe);
500: }
501:
502: } else {
503: final ByteArrayBuffer bos = new ByteArrayBuffer();
504: XMLStreamWriter writer = XMLStreamWriterFactory
505: .create(bos);
506: try {
507: msg.writePayloadTo(writer);
508: writer.flush();
509: } catch (XMLStreamException e) {
510: throw new WebServiceException(e);
511: }
512: return XMLMessage.createDataSource("text/xml", bos
513: .newInputStream());
514: }
515: }
516: }
517:
518: public static DataSource createDataSource(final String contentType,
519: final InputStream is) {
520: return new DataSource() {
521: public InputStream getInputStream() {
522: return is;
523: }
524:
525: public OutputStream getOutputStream() {
526: return null;
527: }
528:
529: public String getContentType() {
530: return contentType;
531: }
532:
533: public String getName() {
534: return "";
535: }
536: };
537: }
538: }
|