/* * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.xml.internal.ws.encoding.xml; import com.sun.istack.internal.NotNull; import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.ContentType; import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimeMultipart; import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream; import com.sun.xml.internal.ws.api.SOAPVersion; import com.sun.xml.internal.ws.api.message.Attachment; import com.sun.xml.internal.ws.api.message.AttachmentSet; import com.sun.xml.internal.ws.api.message.HeaderList; import com.sun.xml.internal.ws.api.message.Message; import com.sun.xml.internal.ws.api.message.Messages; import com.sun.xml.internal.ws.api.message.Packet; import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort; import com.sun.xml.internal.ws.api.pipe.Codec; import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory; import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory; import com.sun.xml.internal.ws.encoding.MimeMultipartParser; import com.sun.xml.internal.ws.encoding.XMLHTTPBindingCodec; import com.sun.xml.internal.ws.message.AbstractMessageImpl; import com.sun.xml.internal.ws.message.EmptyMessageImpl; import com.sun.xml.internal.ws.util.xml.XMLStreamReaderToXMLStreamWriter; import org.xml.sax.ContentHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import javax.activation.DataSource; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.ws.WebServiceException; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * * @author Jitendra Kotamraju */ public final class XMLMessage { // So that SAAJ registers DCHs for MIME types static { new com.sun.xml.internal.messaging.saaj.soap.AttachmentPartImpl(); } private static final int PLAIN_XML_FLAG = 1; // 00001 private static final int MIME_MULTIPART_FLAG = 2; // 00010 private static final int FI_ENCODED_FLAG = 16; // 10000 /** * Finds if the stream has some content or not * * @return null if there is no data * else stream to be used */ private static InputStream hasSomeData(InputStream in) throws IOException { if (in != null) { if (in.available() < 1) { if (!in.markSupported()) { in = new BufferedInputStream(in); } in.mark(1); if (in.read() != -1) { in.reset(); } else { in = null; // No data } } } return in; } /** * Construct a message given a content type and an input stream. */ public static Message create(final String ct, InputStream in) { Message data; try { in = hasSomeData(in); if (in == null) { data = Messages.createEmpty(SOAPVersion.SOAP_11); return data; } if (ct != null) { final ContentType contentType = new ContentType(ct); final int contentTypeId = identifyContentType(contentType); if ((contentTypeId & MIME_MULTIPART_FLAG) != 0) { data = new XMLMultiPart(ct, in); } else if ((contentTypeId & PLAIN_XML_FLAG) != 0) { data = Messages.createUsingPayload(new StreamSource(in), SOAPVersion.SOAP_11); } else { data = new UnknownContent(ct, in); } } else { data = Messages.createEmpty(SOAPVersion.SOAP_11); } } catch(Exception ex) { throw new WebServiceException(ex); } return data; } public static Message create(Source source) { return (source == null) ? Messages.createEmpty(SOAPVersion.SOAP_11) : Messages.createUsingPayload(source, SOAPVersion.SOAP_11); } public static Message create(DataSource ds) { try { return (ds == null) ? Messages.createEmpty(SOAPVersion.SOAP_11) : create(ds.getContentType(), ds.getInputStream()); } catch(IOException ioe) { throw new WebServiceException(ioe); } } public static Message create(Exception e) { return new FaultMessage(SOAPVersion.SOAP_11); } /** * Get the content type ID from the content type. */ private static int getContentId(String ct) { try { final ContentType contentType = new ContentType(ct); return identifyContentType(contentType); } catch(Exception ex) { throw new WebServiceException(ex); } } /** * Return true if the content uses fast infoset. */ public static boolean isFastInfoset(String ct) { return (getContentId(ct) & FI_ENCODED_FLAG) != 0; } /** * Verify a contentType. * * @return * MIME_MULTIPART_FLAG | PLAIN_XML_FLAG * MIME_MULTIPART_FLAG | FI_ENCODED_FLAG; * PLAIN_XML_FLAG * FI_ENCODED_FLAG * */ public static int identifyContentType(ContentType contentType) { String primary = contentType.getPrimaryType(); String sub = contentType.getSubType(); if (primary.equalsIgnoreCase("multipart") && sub.equalsIgnoreCase("related")) { String type = contentType.getParameter("type"); if (type != null) { if (isXMLType(type)) { return MIME_MULTIPART_FLAG | PLAIN_XML_FLAG; } else if (isFastInfosetType(type)) { return MIME_MULTIPART_FLAG | FI_ENCODED_FLAG; } } return 0; } else if (isXMLType(primary, sub)) { return PLAIN_XML_FLAG; } else if (isFastInfosetType(primary, sub)) { return FI_ENCODED_FLAG; } return 0; } protected static boolean isXMLType(@NotNull String primary, @NotNull String sub) { return (primary.equalsIgnoreCase("text") && sub.equalsIgnoreCase("xml")) || (primary.equalsIgnoreCase("application") && sub.equalsIgnoreCase("xml")) || (primary.equalsIgnoreCase("application") && sub.toLowerCase().endsWith("+xml")); } protected static boolean isXMLType(String type) { String lowerType = type.toLowerCase(); return lowerType.startsWith("text/xml") || lowerType.startsWith("application/xml") || (lowerType.startsWith("application/") && (lowerType.indexOf("+xml") != -1)); } protected static boolean isFastInfosetType(String primary, String sub) { return primary.equalsIgnoreCase("application") && sub.equalsIgnoreCase("fastinfoset"); } protected static boolean isFastInfosetType(String type) { return type.toLowerCase().startsWith("application/fastinfoset"); } /** * Access a {@link Message} as a {@link DataSource}. * <p> * A {@link Message} implementation will implement this if the * messages is to be access as data source. * <p> * TODO: consider putting as part of the API. */ public static interface MessageDataSource { /** * Check if the data source has been consumed. * @return true of the data source has been consumed, otherwise false. */ boolean hasUnconsumedDataSource(); /** * Get the data source. * @return the data source. */ DataSource getDataSource(); } /** * Data represented as a multi-part MIME message. * <p> * The root part may be an XML or an FI document. * * This class parses {@link MimeMultipart} lazily. */ public static final class XMLMultiPart extends AbstractMessageImpl implements MessageDataSource { private final DataSource dataSource; private MimeMultipartParser mpp; public XMLMultiPart(final String contentType, final InputStream is) { super(SOAPVersion.SOAP_11); dataSource = createDataSource(contentType, is); } public XMLMultiPart(DataSource dataSource) { super(SOAPVersion.SOAP_11); this.dataSource = dataSource; } public DataSource getDataSource() { assert dataSource != null; return dataSource; } private void convertDataSourceToMessage() { if (mpp == null) { try { mpp = new MimeMultipartParser( dataSource.getInputStream(), dataSource.getContentType()); } catch(IOException ioe) { throw new WebServiceException(ioe); } } } @Override public boolean isOneWay(@NotNull WSDLPort port) { return false; } public boolean isFault() { return false; } public boolean hasHeaders() { return false; } public HeaderList getHeaders() { return new HeaderList(); } @Override public AttachmentSet getAttachments() { convertDataSourceToMessage(); return new XMLAttachmentSet(mpp); } public String getPayloadLocalPart() { throw new UnsupportedOperationException(); } public String getPayloadNamespaceURI() { throw new UnsupportedOperationException(); } public boolean hasPayload() { return true; } public Source readPayloadAsSource() { convertDataSourceToMessage(); return mpp.getRootPart().asSource(); } public XMLStreamReader readPayload() throws XMLStreamException { convertDataSourceToMessage(); return XMLStreamReaderFactory.create( null, mpp.getRootPart().asInputStream(), true); } public void writePayloadTo(XMLStreamWriter sw) { XMLStreamReaderToXMLStreamWriter c = new XMLStreamReaderToXMLStreamWriter(); try { XMLStreamReader r = readPayload(); c.bridge(r, sw); XMLStreamReaderFactory.recycle(r); } catch(Exception e) { throw new RuntimeException(e); } } protected void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment){ throw new UnsupportedOperationException(); } public Message copy() { throw new UnsupportedOperationException(); } public boolean hasUnconsumedDataSource() { return mpp == null; } } private static final class XMLAttachmentSet implements AttachmentSet { private final Map<String, Attachment> attMap; public XMLAttachmentSet(MimeMultipartParser mpp) { // TODO attMap = new HashMap<String, Attachment>(); attMap.putAll(mpp.getAttachmentParts()); } /** * Gets the attachment by the content ID. * * @return null * if no such attachment exist. */ public Attachment get(String contentId) { return attMap.get(contentId); } public boolean isEmpty() { return attMap.isEmpty(); } /** * Returns an iterator over a set of elements of type T. * * @return an Iterator. */ public Iterator<Attachment> iterator() { return attMap.values().iterator(); } public void add(Attachment att) { attMap.put(att.getContentId(), att); } } private static class FaultMessage extends EmptyMessageImpl { public FaultMessage(SOAPVersion version) { super(version); } @Override public boolean isFault() { return true; } } /** * Don't know about this content. It's conent-type is NOT the XML types * we recognize(text/xml, application/xml, multipart/related;text/xml etc). * * This could be used to represent image/jpeg etc */ public static class UnknownContent extends AbstractMessageImpl implements MessageDataSource { private final DataSource ds; private final HeaderList headerList; public UnknownContent(final String ct, final InputStream in) { this(createDataSource(ct,in)); } public UnknownContent(DataSource ds) { super(SOAPVersion.SOAP_11); this.ds = ds; this.headerList = new HeaderList(); } /** * Copy constructor. */ private UnknownContent(UnknownContent that) { super(that.soapVersion); this.ds = that.ds; this.headerList = HeaderList.copy(that.headerList); } public boolean hasUnconsumedDataSource() { return true; } public DataSource getDataSource() { assert ds != null; return ds; } protected void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException { throw new UnsupportedOperationException(); } public boolean hasHeaders() { return false; } public boolean isFault() { return false; } public HeaderList getHeaders() { return headerList; } public String getPayloadLocalPart() { throw new UnsupportedOperationException(); } public String getPayloadNamespaceURI() { throw new UnsupportedOperationException(); } public boolean hasPayload() { return false; } public Source readPayloadAsSource() { return null; } public XMLStreamReader readPayload() throws XMLStreamException { throw new WebServiceException("There isn't XML payload. Shouldn't come here."); } public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException { // No XML. Nothing to do } public Message copy() { return new UnknownContent(this); } } public static DataSource getDataSource(Message msg) { if (msg instanceof MessageDataSource) { return ((MessageDataSource)msg).getDataSource(); } else { AttachmentSet atts = msg.getAttachments(); if (atts != null && !atts.isEmpty()) { final ByteOutputStream bos = new ByteOutputStream(); try { Codec codec = new XMLHTTPBindingCodec(); com.sun.xml.internal.ws.api.pipe.ContentType ct = codec.getStaticContentType(new Packet(msg)); codec.encode(new Packet(msg), bos); return createDataSource(ct.getContentType(), bos.newInputStream()); } catch(IOException ioe) { throw new WebServiceException(ioe); } } else { final ByteOutputStream bos = new ByteOutputStream(); XMLStreamWriter writer = XMLStreamWriterFactory.create(bos); try { msg.writePayloadTo(writer); writer.flush(); } catch (XMLStreamException e) { throw new WebServiceException(e); } return XMLMessage.createDataSource("text/xml", bos.newInputStream()); } } } public static DataSource createDataSource(final String contentType, final InputStream is) { return new DataSource() { public InputStream getInputStream() { return is; } public OutputStream getOutputStream() { return null; } public String getContentType() { return contentType; } public String getName() { return ""; } }; } }