/*
 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.xml.internal.messaging.saaj.soap;

import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.soap.*;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;

import com.sun.xml.internal.messaging.saaj.packaging.mime.Header;
import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.*;
import com.sun.xml.internal.messaging.saaj.packaging.mime.util.*;
import com.sun.xml.internal.messaging.saaj.packaging.mime.MessagingException;

import com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl;
import com.sun.xml.internal.messaging.saaj.soap.impl.EnvelopeImpl;
import com.sun.xml.internal.messaging.saaj.util.*;
import com.sun.xml.internal.org.jvnet.mimepull.MIMEPart;

The message implementation for SOAP messages with attachments. Messages for specific profiles will likely extend this MessageImpl class and add more value for that particular profile.
Author:Anil Vijendran (akv@eng.sun.com), Rajiv Mordani (rajiv.mordani@sun.com), Manveen Kaur (manveen.kaur@sun.com)
/** * The message implementation for SOAP messages with * attachments. Messages for specific profiles will likely extend this * MessageImpl class and add more value for that particular profile. * * @author Anil Vijendran (akv@eng.sun.com) * @author Rajiv Mordani (rajiv.mordani@sun.com) * @author Manveen Kaur (manveen.kaur@sun.com) */
public abstract class MessageImpl extends SOAPMessage implements SOAPConstants { public static final String CONTENT_ID = "Content-ID"; public static final String CONTENT_LOCATION = "Content-Location"; protected static final Logger log = Logger.getLogger(LogDomainConstants.SOAP_DOMAIN, "com.sun.xml.internal.messaging.saaj.soap.LocalStrings"); protected static final int PLAIN_XML_FLAG = 1; // 00001 protected static final int MIME_MULTIPART_FLAG = 2; // 00010 protected static final int SOAP1_1_FLAG = 4; // 00100 protected static final int SOAP1_2_FLAG = 8; // 01000 //protected static final int MIME_MULTIPART_XOP_FLAG = 14; // 01110 protected static final int MIME_MULTIPART_XOP_SOAP1_1_FLAG = 6; // 00110 protected static final int MIME_MULTIPART_XOP_SOAP1_2_FLAG = 10; // 01010 protected static final int XOP_FLAG = 13; // 01101 protected static final int FI_ENCODED_FLAG = 16; // 10000 protected MimeHeaders headers; protected ContentType contentType; protected SOAPPartImpl soapPartImpl; protected FinalArrayList<AttachmentPart> attachments; protected boolean saved = false; protected byte[] messageBytes; protected int messageByteCount; protected Map<String, Object> properties = new HashMap<>(); // used for lazy attachment initialization protected MimeMultipart multiPart = null; protected boolean attachmentsInitialized = false;
True if this part is encoded using Fast Infoset. MIME -> application/fastinfoset
/** * True if this part is encoded using Fast Infoset. * MIME -&gt; application/fastinfoset */
protected boolean isFastInfoset = false;
True if the Accept header of this message includes application/fastinfoset
/** * True if the Accept header of this message includes * application/fastinfoset */
protected boolean acceptFastInfoset = false; protected MimeMultipart mmp = null; // if attachments are present, don't read the entire message in byte stream in saveTo() private boolean optimizeAttachmentProcessing = true; private InputStream inputStreamAfterSaveChanges = null; public static final String LAZY_SOAP_BODY_PARSING = "saaj.lazy.soap.body"; // switch back to old MimeMultipart incase of problem private static boolean switchOffBM = false; private static boolean switchOffLazyAttachment = false; private static boolean useMimePull = false; static { String s = SAAJUtil.getSystemProperty("saaj.mime.optimization"); if ((s != null) && s.equals("false")) { switchOffBM = true; } s = SAAJUtil.getSystemProperty("saaj.lazy.mime.optimization"); if ((s != null) && s.equals("false")) { switchOffLazyAttachment = true; } useMimePull = SAAJUtil.getSystemBoolean("saaj.use.mimepull"); } //property to indicate optimized serialization for lazy attachments private boolean lazyAttachments = false; // most of the times, Content-Types are already all lower cased. // String.toLowerCase() works faster in this case, so even if you // are only doing one comparison, it pays off to use String.toLowerCase() // than String.equalsIgnoreCase(). When you do more than one comparison, // the benefits of String.toLowerCase() dominates. // // // for FI, // use application/fastinfoset for SOAP 1.1 // use application/soap+fastinfoset for SOAP 1.2 // to speed up comparisons, test methods always use lower cases.
Params:
  • primary – must be all lower case
  • sub – must be all lower case
/** * @param primary * must be all lower case * @param sub * must be all lower case */
private static boolean isSoap1_1Type(String primary, String sub) { return primary.equalsIgnoreCase("text") && sub.equalsIgnoreCase("xml") || primary.equalsIgnoreCase("text") && sub.equalsIgnoreCase("xml-soap") || primary.equals("application") && sub.equals("fastinfoset"); }
Params:
  • type – must be all lower case
/** * @param type * must be all lower case */
private static boolean isEqualToSoap1_1Type(String type) { return type.startsWith("text/xml") || type.startsWith("application/fastinfoset"); }
Params:
  • primary – must be all lower case
  • sub – must be all lower case
/** * @param primary * must be all lower case * @param sub * must be all lower case */
private static boolean isSoap1_2Type(String primary, String sub) { return primary.equals("application") && (sub.equals("soap+xml") || sub.equals("soap+fastinfoset")); }
Params:
  • type – must be all lower case
/** * @param type * must be all lower case */
private static boolean isEqualToSoap1_2Type(String type) { return type.startsWith("application/soap+xml") || type.startsWith("application/soap+fastinfoset"); }
Construct a new message. This will be invoked before message sends.
/** * Construct a new message. This will be invoked before message * sends. */
protected MessageImpl() { this(false, false); attachmentsInitialized = true; }
Construct a new message. This will be invoked before message sends.
Params:
  • isFastInfoset – whether it is fast infoset
  • acceptFastInfoset – whether to accept fast infoset
/** * Construct a new message. This will be invoked before message * sends. * * @param isFastInfoset whether it is fast infoset * @param acceptFastInfoset whether to accept fast infoset */
protected MessageImpl(boolean isFastInfoset, boolean acceptFastInfoset) { this.isFastInfoset = isFastInfoset; this.acceptFastInfoset = acceptFastInfoset; headers = new MimeHeaders(); headers.setHeader("Accept", getExpectedAcceptHeader()); contentType = new ContentType(); }
Shallow copy.
Params:
  • msg – SoapMessage
/** * Shallow copy. * * @param msg SoapMessage */
protected MessageImpl(SOAPMessage msg) { if (!(msg instanceof MessageImpl)) { // don't know how to handle this. } MessageImpl src = (MessageImpl) msg; this.headers = src.headers; this.soapPartImpl = src.soapPartImpl; this.attachments = src.attachments; this.saved = src.saved; this.messageBytes = src.messageBytes; this.messageByteCount = src.messageByteCount; this.properties = src.properties; this.contentType = src.contentType; }
Params:
Returns:true if SOAP 1.1 Content
/** * @param stat * the mask value obtained from {@link #identifyContentType(ContentType)} * @return true if SOAP 1.1 Content */
protected static boolean isSoap1_1Content(int stat) { return (stat & SOAP1_1_FLAG) != 0; }
Check whether it is SOAP 1.2 content.
Params:
Returns:true if it is SOAP 1.2 content
/** * Check whether it is SOAP 1.2 content. * @param stat * the mask value obtained from {@link #identifyContentType(ContentType)} * @return true if it is SOAP 1.2 content */
protected static boolean isSoap1_2Content(int stat) { return (stat & SOAP1_2_FLAG) != 0; } private static boolean isMimeMultipartXOPSoap1_2Package(ContentType contentType) { String type = contentType.getParameter("type"); if (type == null) { return false; } type = type.toLowerCase(); if (!type.startsWith("application/xop+xml")) { return false; } String startinfo = contentType.getParameter("start-info"); if (startinfo == null) { return false; } startinfo = startinfo.toLowerCase(); return isEqualToSoap1_2Type(startinfo); } //private static boolean isMimeMultipartXOPPackage(ContentType contentType) { private static boolean isMimeMultipartXOPSoap1_1Package(ContentType contentType) { String type = contentType.getParameter("type"); if(type==null) return false; type = type.toLowerCase(); if(!type.startsWith("application/xop+xml")) return false; String startinfo = contentType.getParameter("start-info"); if(startinfo == null) return false; startinfo = startinfo.toLowerCase(); return isEqualToSoap1_1Type(startinfo); } private static boolean isSOAPBodyXOPPackage(ContentType contentType){ String primary = contentType.getPrimaryType(); String sub = contentType.getSubType(); if (primary.equalsIgnoreCase("application")) { if (sub.equalsIgnoreCase("xop+xml")) { String type = getTypeParameter(contentType); return isEqualToSoap1_2Type(type) || isEqualToSoap1_1Type(type); } } return false; }
Construct a message from an input stream. When messages are received, there's two parts -- the transport headers and the message content in a transport specific stream.
Params:
  • headers – MimeHeaders
  • in – InputStream
Throws:
/** * Construct a message from an input stream. When messages are * received, there's two parts -- the transport headers and the * message content in a transport specific stream. * @param headers MimeHeaders * @param in InputStream * @exception SOAPExceptionImpl in case of I/O error */
protected MessageImpl(MimeHeaders headers, final InputStream in) throws SOAPExceptionImpl { contentType = parseContentType(headers); init(headers,identifyContentType(contentType),contentType,in); } private static ContentType parseContentType(MimeHeaders headers) throws SOAPExceptionImpl { final String ct; if (headers != null) ct = getContentType(headers); else { log.severe("SAAJ0550.soap.null.headers"); throw new SOAPExceptionImpl("Cannot create message: " + "Headers can't be null"); } if (ct == null) { log.severe("SAAJ0532.soap.no.Content-Type"); throw new SOAPExceptionImpl("Absent Content-Type"); } try { return new ContentType(ct); } catch (Throwable ex) { log.severe("SAAJ0535.soap.cannot.internalize.message"); throw new SOAPExceptionImpl("Unable to internalize message", ex); } }
Construct a message from an input stream. When messages are received, there's two parts -- the transport headers and the message content in a transport specific stream.
Params:
  • headers – headers
  • contentType – The parsed content type header from the headers variable. This is redundant parameter, but it avoids reparsing this header again.
  • stat – The result of identifyContentType(ContentType) over the contentType parameter. This redundant parameter, but it avoids recomputing this information again.
  • in – input stream
Throws:
/** * Construct a message from an input stream. When messages are * received, there's two parts -- the transport headers and the * message content in a transport specific stream. * * @param headers headers * @param contentType * The parsed content type header from the headers variable. * This is redundant parameter, but it avoids reparsing this header again. * @param stat * The result of {@link #identifyContentType(ContentType)} over * the contentType parameter. This redundant parameter, but it avoids * recomputing this information again. * @param in input stream * @exception SOAPExceptionImpl in case of an error */
protected MessageImpl(MimeHeaders headers, final ContentType contentType, int stat, final InputStream in) throws SOAPExceptionImpl { init(headers, stat, contentType, in); } public MessageImpl(MimeHeaders headers, ContentType ct, int stat, XMLStreamReader reader) throws SOAPExceptionImpl { init(headers, stat, ct, reader); } private void init(MimeHeaders headers, int stat, final ContentType contentType, final Object input) throws SOAPExceptionImpl { this.headers = headers; try { // Set isFastInfoset/acceptFastInfoset flag based on MIME type if ((stat & FI_ENCODED_FLAG) > 0) { isFastInfoset = acceptFastInfoset = true; } // If necessary, inspect Accept header to set acceptFastInfoset if (!isFastInfoset) { String[] values = headers.getHeader("Accept"); if (values != null) { for (int i = 0; i < values.length; i++) { StringTokenizer st = new StringTokenizer(values[i], ","); while (st.hasMoreTokens()) { final String token = st.nextToken().trim(); if (token.equalsIgnoreCase("application/fastinfoset") || token.equalsIgnoreCase("application/soap+fastinfoset")) { acceptFastInfoset = true; break; } } } } } if (!isCorrectSoapVersion(stat)) { log.log( Level.SEVERE, "SAAJ0533.soap.incorrect.Content-Type", new String[] { contentType.toString(), getExpectedContentType()}); throw new SOAPVersionMismatchException( "Cannot create message: incorrect content-type for SOAP version. Got: " + contentType + " Expected: " + getExpectedContentType()); } InputStream in = null; XMLStreamReader rdr = null; if (input instanceof InputStream) { in = (InputStream) input; } else { //is a StAX reader rdr = (XMLStreamReader) input; } if ((stat & PLAIN_XML_FLAG) != 0) { if (in != null) { if (isFastInfoset) { getSOAPPart().setContent( FastInfosetReflection.FastInfosetSource_new(in)); } else { initCharsetProperty(contentType); getSOAPPart().setContent(new StreamSource(in)); } } else { //is a StAX reader if (isFastInfoset) { //need to get FI stax reader } else { initCharsetProperty(contentType); getSOAPPart().setContent(new StAXSource(rdr)); } } } else if ((stat & MIME_MULTIPART_FLAG) != 0 && in == null) { //only parse multipart in the inputstream case //in stax reader case, we would be given the attachments separately getSOAPPart().setContent(new StAXSource(rdr)); } else if ((stat & MIME_MULTIPART_FLAG) != 0) { final InputStream finalIn = in; DataSource ds = new DataSource() { @Override public InputStream getInputStream() { return finalIn; } @Override public OutputStream getOutputStream() { return null; } @Override public String getContentType() { return contentType.toString(); } @Override public String getName() { return ""; } }; multiPart = null; if (useMimePull) { multiPart = new MimePullMultipart(ds,contentType); } else if (switchOffBM) { multiPart = new MimeMultipart(ds,contentType); } else { multiPart = new BMMimeMultipart(ds,contentType); } String startParam = contentType.getParameter("start"); MimeBodyPart soapMessagePart = null; InputStream soapPartInputStream = null; String contentID = null; String contentIDNoAngle = null; if (switchOffBM || switchOffLazyAttachment) { if(startParam == null) { soapMessagePart = multiPart.getBodyPart(0); for (int i = 1; i < multiPart.getCount(); i++) { initializeAttachment(multiPart, i); } } else { soapMessagePart = multiPart.getBodyPart(startParam); for (int i = 0; i < multiPart.getCount(); i++) { contentID = multiPart.getBodyPart(i).getContentID(); // Old versions of AXIS2 put angle brackets around the content // id but not the start param contentIDNoAngle = (contentID != null) ? contentID.replaceFirst("^<", "").replaceFirst(">$", "") : null; if(!startParam.equals(contentID) && !startParam.equals(contentIDNoAngle)) initializeAttachment(multiPart, i); } } } else { if (useMimePull) { MimePullMultipart mpMultipart = (MimePullMultipart)multiPart; MIMEPart sp = mpMultipart.readAndReturnSOAPPart(); soapMessagePart = new MimeBodyPart(sp); soapPartInputStream = sp.readOnce(); } else { BMMimeMultipart bmMultipart = (BMMimeMultipart) multiPart; InputStream stream = bmMultipart.initStream(); SharedInputStream sin = null; if (stream instanceof SharedInputStream) { sin = (SharedInputStream) stream; } String boundary = "--" + contentType.getParameter("boundary"); byte[] bndbytes = ASCIIUtility.getBytes(boundary); if (startParam == null) { soapMessagePart = bmMultipart.getNextPart(stream, bndbytes, sin); bmMultipart.removeBodyPart(soapMessagePart); } else { MimeBodyPart bp = null; try { while (!startParam.equals(contentID) && !startParam.equals(contentIDNoAngle)) { bp = bmMultipart.getNextPart( stream, bndbytes, sin); contentID = bp.getContentID(); // Old versions of AXIS2 put angle brackets around the content // id but not the start param contentIDNoAngle = (contentID != null) ? contentID.replaceFirst("^<", "").replaceFirst(">$", "") : null; } soapMessagePart = bp; bmMultipart.removeBodyPart(bp); } catch (Exception e) { throw new SOAPExceptionImpl(e); } } } } // findbugs correctly points out that we'd NPE instantiating // the ContentType (just below here) if soapMessagePart were // null. Hence are better off throwing a controlled exception // at this point if it is null. if (soapMessagePart == null) { log.severe("SAAJ0510.soap.cannot.create.envelope"); throw new SOAPExceptionImpl( "Unable to create envelope from given source: SOAP part not found"); } if (soapPartInputStream == null) { soapPartInputStream = soapMessagePart.getInputStream(); } ContentType soapPartCType = new ContentType( soapMessagePart.getContentType()); initCharsetProperty(soapPartCType); String baseType = soapPartCType.getBaseType().toLowerCase(); if(!(isEqualToSoap1_1Type(baseType) || isEqualToSoap1_2Type(baseType) || isSOAPBodyXOPPackage(soapPartCType))) { log.log(Level.SEVERE, "SAAJ0549.soap.part.invalid.Content-Type", new Object[] {baseType}); throw new SOAPExceptionImpl( "Bad Content-Type for SOAP Part : " + baseType); } SOAPPart soapPart = getSOAPPart(); setMimeHeaders(soapPart, soapMessagePart); soapPart.setContent(isFastInfoset ? (Source) FastInfosetReflection.FastInfosetSource_new( soapPartInputStream) : (Source) new StreamSource(soapPartInputStream)); } else { log.severe("SAAJ0534.soap.unknown.Content-Type"); throw new SOAPExceptionImpl("Unrecognized Content-Type"); } } catch (Throwable ex) { log.severe("SAAJ0535.soap.cannot.internalize.message"); throw new SOAPExceptionImpl("Unable to internalize message", ex); } needsSave(); } public boolean isFastInfoset() { return isFastInfoset; } public boolean acceptFastInfoset() { return acceptFastInfoset; } public void setIsFastInfoset(boolean value) { if (value != isFastInfoset) { isFastInfoset = value; if (isFastInfoset) { acceptFastInfoset = true; } saved = false; // ensure transcoding if necessary } } public boolean isLazySoapBodyParsing() { Object lazyParsingProp = getProperty(LAZY_SOAP_BODY_PARSING); if (lazyParsingProp == null) return false; if (lazyParsingProp instanceof Boolean) { return ((Boolean) lazyParsingProp).booleanValue(); } else { return Boolean.valueOf(lazyParsingProp.toString()); } } @Override public Object getProperty(String property) { return properties.get(property); } @Override public void setProperty(String property, Object value) { verify(property, value); properties.put(property, value); } private void verify(String property, Object value) { if (property.equalsIgnoreCase(SOAPMessage.WRITE_XML_DECLARATION)) { if (!("true".equals(value) || "false".equals(value))) throw new RuntimeException( property + " must have value false or true"); try { EnvelopeImpl env = (EnvelopeImpl) getSOAPPart().getEnvelope(); if ("true".equalsIgnoreCase((String)value)) { env.setOmitXmlDecl("no"); } else if ("false".equalsIgnoreCase((String)value)) { env.setOmitXmlDecl("yes"); } } catch (Exception e) { log.log(Level.SEVERE, "SAAJ0591.soap.exception.in.set.property", new Object[] {e.getMessage(), "javax.xml.soap.write-xml-declaration"}); throw new RuntimeException(e); } return; } if (property.equalsIgnoreCase(SOAPMessage.CHARACTER_SET_ENCODING)) { try { ((EnvelopeImpl) getSOAPPart().getEnvelope()).setCharsetEncoding((String)value); } catch (Exception e) { log.log(Level.SEVERE, "SAAJ0591.soap.exception.in.set.property", new Object[] {e.getMessage(), "javax.xml.soap.character-set-encoding"}); throw new RuntimeException(e); } } } protected abstract boolean isCorrectSoapVersion(int contentTypeId); protected abstract String getExpectedContentType(); protected abstract String getExpectedAcceptHeader();
Sniffs the Content-Type header so that we can determine how to process.

In the absence of type attribute we assume it to be text/xml. That would mean we're easy on accepting the message and generate the correct thing (as the SWA spec also specifies that the type parameter should always be text/xml)

Returns: combination of flags, such as PLAIN_XML_CODE and MIME_MULTIPART_CODE.
/** * Sniffs the Content-Type header so that we can determine how to process. * * <p> * In the absence of type attribute we assume it to be text/xml. * That would mean we're easy on accepting the message and * generate the correct thing (as the SWA spec also specifies * that the type parameter should always be text/xml) * * @return * combination of flags, such as PLAIN_XML_CODE and MIME_MULTIPART_CODE. */
// SOAP1.2 allow SOAP1.2 content type static int identifyContentType(ContentType ct) throws SOAPExceptionImpl { // TBD // Is there anything else we need to verify here? String primary = ct.getPrimaryType().toLowerCase(); String sub = ct.getSubType().toLowerCase(); if (primary.equals("multipart")) { if (sub.equals("related")) { String type = getTypeParameter(ct); if (isEqualToSoap1_1Type(type)) { return (type.equals("application/fastinfoset") ? FI_ENCODED_FLAG : 0) | MIME_MULTIPART_FLAG | SOAP1_1_FLAG; } else if (isEqualToSoap1_2Type(type)) { return (type.equals("application/soap+fastinfoset") ? FI_ENCODED_FLAG : 0) | MIME_MULTIPART_FLAG | SOAP1_2_FLAG; /*} else if (isMimeMultipartXOPPackage(ct)) { return MIME_MULTIPART_XOP_FLAG;*/ } else if (isMimeMultipartXOPSoap1_1Package(ct)) { return MIME_MULTIPART_XOP_SOAP1_1_FLAG; } else if (isMimeMultipartXOPSoap1_2Package(ct)) { return MIME_MULTIPART_XOP_SOAP1_2_FLAG; } else { log.severe("SAAJ0536.soap.content-type.mustbe.multipart"); throw new SOAPExceptionImpl( "Content-Type needs to be Multipart/Related " + "and with \"type=text/xml\" " + "or \"type=application/soap+xml\""); } } else { log.severe("SAAJ0537.soap.invalid.content-type"); throw new SOAPExceptionImpl( "Invalid Content-Type: " + primary + '/' + sub); } } else if (isSoap1_1Type(primary, sub)) { return (primary.equalsIgnoreCase("application") && sub.equalsIgnoreCase("fastinfoset") ? FI_ENCODED_FLAG : 0) | PLAIN_XML_FLAG | SOAP1_1_FLAG; } else if (isSoap1_2Type(primary, sub)) { return (primary.equalsIgnoreCase("application") && sub.equalsIgnoreCase("soap+fastinfoset") ? FI_ENCODED_FLAG : 0) | PLAIN_XML_FLAG | SOAP1_2_FLAG; } else if(isSOAPBodyXOPPackage(ct)){ return XOP_FLAG; } else { log.severe("SAAJ0537.soap.invalid.content-type"); throw new SOAPExceptionImpl( "Invalid Content-Type:" + primary + '/' + sub + ". Is this an error message instead of a SOAP response?"); } }
Obtains the type parameter of the Content-Type header. Defaults to "text/xml".
/** * Obtains the type parameter of the Content-Type header. Defaults to "text/xml". */
private static String getTypeParameter(ContentType contentType) { String p = contentType.getParameter("type"); if(p!=null) return p.toLowerCase(); else return "text/xml"; } @Override public MimeHeaders getMimeHeaders() { return this.headers; } final static String getContentType(MimeHeaders headers) { String[] values = headers.getHeader("Content-Type"); if (values == null) return null; else return values[0]; } /* * Get the complete ContentType value along with optional parameters. */ public String getContentType() { return getContentType(this.headers); } public void setContentType(String type) { headers.setHeader("Content-Type", type); needsSave(); } private ContentType contentType() { ContentType ct = null; try { String currentContent = getContentType(); if (currentContent == null) { return this.contentType; } ct = new ContentType(currentContent); } catch (Exception e) { // what to do here? } return ct; } /* * Return the MIME type string, without the parameters. */ public String getBaseType() { return contentType().getBaseType(); } public void setBaseType(String type) { ContentType ct = contentType(); ct.setParameter("type", type); headers.setHeader("Content-Type", ct.toString()); needsSave(); } public String getAction() { return contentType().getParameter("action"); } public void setAction(String action) { ContentType ct = contentType(); ct.setParameter("action", action); headers.setHeader("Content-Type", ct.toString()); needsSave(); } public String getCharset() { return contentType().getParameter("charset"); } public void setCharset(String charset) { ContentType ct = contentType(); ct.setParameter("charset", charset); headers.setHeader("Content-Type", ct.toString()); needsSave(); }
All write methods (i.e setters) should call this method in order to make sure that a save is necessary since the state has been modified.
/** * All write methods (i.e setters) should call this method in * order to make sure that a save is necessary since the state * has been modified. */
private final void needsSave() { saved = false; } @Override public boolean saveRequired() { return saved != true; } @Override public String getContentDescription() { String[] values = headers.getHeader("Content-Description"); if (values != null && values.length > 0) return values[0]; return null; } @Override public void setContentDescription(String description) { headers.setHeader("Content-Description", description); needsSave(); } @Override public abstract SOAPPart getSOAPPart(); @Override public void removeAllAttachments() { try { initializeAllAttachments(); } catch (Exception e) { throw new RuntimeException(e); } if (attachments != null) { attachments.clear(); needsSave(); } } @Override public int countAttachments() { try { initializeAllAttachments(); } catch (Exception e) { throw new RuntimeException(e); } if (attachments != null) return attachments.size(); return 0; } @Override public void addAttachmentPart(AttachmentPart attachment) { try { initializeAllAttachments(); this.optimizeAttachmentProcessing = true; } catch (Exception e) { throw new RuntimeException(e); } if (attachments == null) attachments = new FinalArrayList<>(); attachments.add(attachment); needsSave(); } static private final Iterator<AttachmentPart> nullIter = Collections.<AttachmentPart>EMPTY_LIST.iterator(); @Override public Iterator<AttachmentPart> getAttachments() { try { initializeAllAttachments(); } catch (Exception e) { throw new RuntimeException(e); } if (attachments == null) return nullIter; return attachments.iterator(); } private void setFinalContentType(String charset) { ContentType ct = contentType(); if (ct == null) { ct = new ContentType(); } String[] split = getExpectedContentType().split("/"); ct.setPrimaryType(split[0]); ct.setSubType(split[1]); ct.setParameter("charset", charset); headers.setHeader("Content-Type", ct.toString()); } private class MimeMatchingIterator implements Iterator<AttachmentPart> { public MimeMatchingIterator(MimeHeaders headers) { this.headers = headers; this.iter = attachments.iterator(); } private Iterator<AttachmentPart> iter; private MimeHeaders headers; private AttachmentPart nextAttachment; @Override public boolean hasNext() { if (nextAttachment == null) nextAttachment = nextMatch(); return nextAttachment != null; } @Override public AttachmentPart next() { if (nextAttachment != null) { AttachmentPart ret = nextAttachment; nextAttachment = null; return ret; } if (hasNext()) return nextAttachment; return null; } AttachmentPart nextMatch() { while (iter.hasNext()) { AttachmentPartImpl ap = (AttachmentPartImpl) iter.next(); if (ap.hasAllHeaders(headers)) return ap; } return null; } @Override public void remove() { iter.remove(); } } @Override public Iterator<AttachmentPart> getAttachments(MimeHeaders headers) { try { initializeAllAttachments(); } catch (Exception e) { throw new RuntimeException(e); } if (attachments == null) return nullIter; return new MimeMatchingIterator(headers); } @Override public void removeAttachments(MimeHeaders headers) { try { initializeAllAttachments(); } catch (Exception e) { throw new RuntimeException(e); } if (attachments == null) return ; Iterator<AttachmentPart> it = new MimeMatchingIterator(headers); while (it.hasNext()) { int index = attachments.indexOf(it.next()); attachments.set(index, null); } FinalArrayList<AttachmentPart> f = new FinalArrayList<AttachmentPart>(); for (int i = 0; i < attachments.size(); i++) { if (attachments.get(i) != null) { f.add(attachments.get(i)); } } attachments = f; // needsSave(); } @Override public AttachmentPart createAttachmentPart() { return new AttachmentPartImpl(); } @Override public AttachmentPart getAttachment(SOAPElement element) throws SOAPException { try { initializeAllAttachments(); } catch (Exception e) { throw new RuntimeException(e); } String uri; String hrefAttr = element.getAttribute("href"); if ("".equals(hrefAttr)) { Node node = getValueNodeStrict(element); String swaRef = null; if (node != null) { swaRef = node.getValue(); } if (swaRef == null || "".equals(swaRef)) { return null; } else { uri = swaRef; } } else { uri = hrefAttr; } return getAttachmentPart(uri); } private Node getValueNodeStrict(SOAPElement element) { Node node = (Node)element.getFirstChild(); if (node != null) { if (node.getNextSibling() == null && node.getNodeType() == org.w3c.dom.Node.TEXT_NODE) { return node; } else { return null; } } return null; } private AttachmentPart getAttachmentPart(String uri) throws SOAPException { AttachmentPart _part; try { if (uri.startsWith("cid:")) { // rfc2392 uri = '<'+uri.substring("cid:".length())+'>'; MimeHeaders headersToMatch = new MimeHeaders(); headersToMatch.addHeader(CONTENT_ID, uri); Iterator i = this.getAttachments(headersToMatch); _part = (i == null) ? null : (AttachmentPart)i.next(); } else { // try content-location MimeHeaders headersToMatch = new MimeHeaders(); headersToMatch.addHeader(CONTENT_LOCATION, uri); Iterator i = this.getAttachments(headersToMatch); _part = (i == null) ? null : (AttachmentPart)i.next(); } // try auto-generated JAXRPC CID if (_part == null) { Iterator j = this.getAttachments(); while (j.hasNext()) { AttachmentPart p = (AttachmentPart)j.next(); String cl = p.getContentId(); if (cl != null) { // obtain the partname int eqIndex = cl.indexOf("="); if (eqIndex > -1) { cl = cl.substring(1, eqIndex); if (cl.equalsIgnoreCase(uri)) { _part = p; break; } } } } } } catch (Exception se) { log.log(Level.SEVERE, "SAAJ0590.soap.unable.to.locate.attachment", new Object[] {uri}); throw new SOAPExceptionImpl(se); } return _part; } private final InputStream getHeaderBytes() throws IOException { SOAPPartImpl sp = (SOAPPartImpl) getSOAPPart(); return sp.getContentAsStream(); } private String convertToSingleLine(String contentType) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < contentType.length(); i ++) { char c = contentType.charAt(i); if (c != '\r' && c != '\n' && c != '\t') buffer.append(c); } return buffer.toString(); } private MimeMultipart getMimeMessage() throws SOAPException { try { SOAPPartImpl soapPart = (SOAPPartImpl) getSOAPPart(); MimeBodyPart mimeSoapPart = soapPart.getMimePart(); /* * Get content type from this message instead of soapPart * to ensure agreement if soapPart is transcoded (XML <-> FI) */ ContentType soapPartCtype = new ContentType(getExpectedContentType()); if (!isFastInfoset) { soapPartCtype.setParameter("charset", initCharset()); } mimeSoapPart.setHeader("Content-Type", soapPartCtype.toString()); MimeMultipart headerAndBody = null; if (!switchOffBM && !switchOffLazyAttachment && (multiPart != null) && !attachmentsInitialized) { headerAndBody = new BMMimeMultipart(); headerAndBody.addBodyPart(mimeSoapPart); if (attachments != null) { for (Iterator<AttachmentPart> eachAttachment = attachments.iterator(); eachAttachment.hasNext();) { headerAndBody.addBodyPart( ((AttachmentPartImpl) eachAttachment.next()) .getMimePart()); } } InputStream in = ((BMMimeMultipart)multiPart).getInputStream(); if (!((BMMimeMultipart)multiPart).lastBodyPartFound() && !((BMMimeMultipart)multiPart).isEndOfStream()) { ((BMMimeMultipart)headerAndBody).setInputStream(in); ((BMMimeMultipart)headerAndBody).setBoundary( ((BMMimeMultipart)multiPart).getBoundary()); ((BMMimeMultipart)headerAndBody). setLazyAttachments(lazyAttachments); } } else { headerAndBody = new MimeMultipart(); headerAndBody.addBodyPart(mimeSoapPart); for (Iterator eachAttachement = getAttachments(); eachAttachement.hasNext(); ) { headerAndBody.addBodyPart( ((AttachmentPartImpl) eachAttachement.next()) .getMimePart()); } } ContentType contentType = headerAndBody.getContentType(); ParameterList l = contentType.getParameterList(); // set content type depending on SOAP version l.set("type", getExpectedContentType()); l.set("boundary", contentType.getParameter("boundary")); ContentType nct = new ContentType("multipart", "related", l); headers.setHeader( "Content-Type", convertToSingleLine(nct.toString())); // TBD // Set content length MIME header here. return headerAndBody; } catch (SOAPException ex) { throw ex; } catch (Throwable ex) { log.severe("SAAJ0538.soap.cannot.convert.msg.to.multipart.obj"); throw new SOAPExceptionImpl( "Unable to convert SOAP message into " + "a MimeMultipart object", ex); } } private String initCharset() { String charset = null; String[] cts = getMimeHeaders().getHeader("Content-Type"); if ((cts != null) && (cts[0] != null)) { charset = getCharsetString(cts[0]); } if (charset == null) { charset = (String) getProperty(CHARACTER_SET_ENCODING); } if (charset != null) { return charset; } return "utf-8"; } private String getCharsetString(String s) { try { int index = s.indexOf(";"); if(index < 0) return null; ParameterList pl = new ParameterList(s.substring(index)); return pl.get("charset"); } catch(Exception e) { return null; } } @Override public void saveChanges() throws SOAPException { // suck in all the data from the attachments and have it // ready for writing/sending etc. String charset = initCharset(); /*if (countAttachments() == 0) {*/ int attachmentCount = (attachments == null) ? 0 : attachments.size(); if (attachmentCount == 0) { if (!switchOffBM && !switchOffLazyAttachment && !attachmentsInitialized && (multiPart != null)) { // so there might be attachments attachmentCount = 1; } } try { if ((attachmentCount == 0) && !hasXOPContent()) { InputStream in; try{ /* * Not sure why this is called getHeaderBytes(), but it actually * returns the whole message as a byte stream. This stream could * be either XML of Fast depending on the mode. */ in = getHeaderBytes(); // no attachments, hence this property can be false this.optimizeAttachmentProcessing = false; if (SOAPPartImpl.lazyContentLength) { inputStreamAfterSaveChanges = in; } } catch (IOException ex) { log.severe("SAAJ0539.soap.cannot.get.header.stream"); throw new SOAPExceptionImpl( "Unable to get header stream in saveChanges: ", ex); } if (in instanceof ByteInputStream) { ByteInputStream bIn = (ByteInputStream)in; messageBytes = bIn.getBytes(); messageByteCount = bIn.getCount(); } setFinalContentType(charset); /* headers.setHeader( "Content-Type", getExpectedContentType() + (isFastInfoset ? "" : "; charset=" + charset));*/ if (messageByteCount > 0) { headers.setHeader( "Content-Length", Integer.toString(messageByteCount)); } } else { if(hasXOPContent()) mmp = getXOPMessage(); else mmp = getMimeMessage(); } } catch (Throwable ex) { log.severe("SAAJ0540.soap.err.saving.multipart.msg"); throw new SOAPExceptionImpl( "Error during saving a multipart message", ex); } // FIX ME -- SOAP Action replaced by Content-Type optional parameter action /* if(isCorrectSoapVersion(SOAP1_1_FLAG)) { String[] soapAction = headers.getHeader("SOAPAction"); if (soapAction == null || soapAction.length == 0) headers.setHeader("SOAPAction", "\"\""); } */ saved = true; } private MimeMultipart getXOPMessage() throws SOAPException { try { MimeMultipart headerAndBody = new MimeMultipart(); SOAPPartImpl soapPart = (SOAPPartImpl)getSOAPPart(); MimeBodyPart mimeSoapPart = soapPart.getMimePart(); ContentType soapPartCtype = new ContentType("application/xop+xml"); soapPartCtype.setParameter("type", getExpectedContentType()); String charset = initCharset(); soapPartCtype.setParameter("charset", charset); mimeSoapPart.setHeader("Content-Type", soapPartCtype.toString()); headerAndBody.addBodyPart(mimeSoapPart); for (Iterator eachAttachement = getAttachments(); eachAttachement.hasNext(); ) { headerAndBody.addBodyPart( ((AttachmentPartImpl) eachAttachement.next()) .getMimePart()); } ContentType contentType = headerAndBody.getContentType(); ParameterList l = contentType.getParameterList(); //lets not write start-info for now till we get servlet fix done l.set("start-info", getExpectedContentType());//+";charset="+initCharset()); // set content type depending on SOAP version l.set("type", "application/xop+xml"); if (isCorrectSoapVersion(SOAP1_2_FLAG)) { String action = getAction(); if(action != null) l.set("action", action); } l.set("boundary", contentType.getParameter("boundary")); ContentType nct = new ContentType("Multipart", "Related", l); headers.setHeader( "Content-Type", convertToSingleLine(nct.toString())); // TBD // Set content length MIME header here. return headerAndBody; } catch (SOAPException ex) { throw ex; } catch (Throwable ex) { log.severe("SAAJ0538.soap.cannot.convert.msg.to.multipart.obj"); throw new SOAPExceptionImpl( "Unable to convert SOAP message into " + "a MimeMultipart object", ex); } } private boolean hasXOPContent() throws ParseException { String type = getContentType(); if(type == null) return false; ContentType ct = new ContentType(type); //return isMimeMultipartXOPPackage(ct) || isSOAPBodyXOPPackage(ct); return isMimeMultipartXOPSoap1_1Package(ct) || isMimeMultipartXOPSoap1_2Package(ct) || isSOAPBodyXOPPackage(ct); } @Override public void writeTo(OutputStream out) throws SOAPException, IOException { if (saveRequired()){ this.optimizeAttachmentProcessing = true; saveChanges(); } if(!optimizeAttachmentProcessing){ if (SOAPPartImpl.lazyContentLength && messageByteCount <= 0) { byte[] buf = new byte[1024]; int length = 0; while( (length = inputStreamAfterSaveChanges.read(buf)) != -1) { out.write(buf,0, length); messageByteCount += length; } if (messageByteCount > 0) { headers.setHeader( "Content-Length", Integer.toString(messageByteCount)); } } else { out.write(messageBytes, 0, messageByteCount); } } else{ try{ if(hasXOPContent()){ mmp.writeTo(out); }else{ mmp.writeTo(out); if (!switchOffBM && !switchOffLazyAttachment && (multiPart != null) && !attachmentsInitialized) { ((BMMimeMultipart)multiPart).setInputStream( ((BMMimeMultipart)mmp).getInputStream()); } } } catch(Exception ex){ log.severe("SAAJ0540.soap.err.saving.multipart.msg"); throw new SOAPExceptionImpl( "Error during saving a multipart message", ex); } } if(isCorrectSoapVersion(SOAP1_1_FLAG)) { String[] soapAction = headers.getHeader("SOAPAction"); if (soapAction == null || soapAction.length == 0) headers.setHeader("SOAPAction", "\"\""); } messageBytes = null; needsSave(); } @Override public SOAPBody getSOAPBody() throws SOAPException { SOAPBody body = getSOAPPart().getEnvelope().getBody(); /*if (body == null) { throw new SOAPException("No SOAP Body was found in the SOAP Message"); }*/ return body; } @Override public SOAPHeader getSOAPHeader() throws SOAPException { SOAPHeader hdr = getSOAPPart().getEnvelope().getHeader(); /*if (hdr == null) { throw new SOAPException("No SOAP Header was found in the SOAP Message"); }*/ return hdr; } private void initializeAllAttachments () throws MessagingException, SOAPException { if (switchOffBM || switchOffLazyAttachment) { return; } if (attachmentsInitialized || (multiPart == null)) { return; } if (attachments == null) attachments = new FinalArrayList<AttachmentPart>(); int count = multiPart.getCount(); for (int i=0; i < count; i++ ) { initializeAttachment(multiPart.getBodyPart(i)); } attachmentsInitialized = true; //multiPart = null; needsSave(); } private void initializeAttachment(MimeBodyPart mbp) throws SOAPException { AttachmentPartImpl attachmentPart = new AttachmentPartImpl(); DataHandler attachmentHandler = mbp.getDataHandler(); attachmentPart.setDataHandler(attachmentHandler); AttachmentPartImpl.copyMimeHeaders(mbp, attachmentPart); attachments.add(attachmentPart); } private void initializeAttachment(MimeMultipart multiPart, int i) throws Exception { MimeBodyPart currentBodyPart = multiPart.getBodyPart(i); AttachmentPartImpl attachmentPart = new AttachmentPartImpl(); DataHandler attachmentHandler = currentBodyPart.getDataHandler(); attachmentPart.setDataHandler(attachmentHandler); AttachmentPartImpl.copyMimeHeaders(currentBodyPart, attachmentPart); addAttachmentPart(attachmentPart); } private void setMimeHeaders(SOAPPart soapPart, MimeBodyPart soapMessagePart) throws Exception { // first remove the existing content-type soapPart.removeAllMimeHeaders(); // add everything present in soapMessagePart List headers = soapMessagePart.getAllHeaders(); int sz = headers.size(); for( int i=0; i<sz; i++ ) { Header h = (Header) headers.get(i); soapPart.addMimeHeader(h.getName(), h.getValue()); } } private void initCharsetProperty(ContentType contentType) { String charset = contentType.getParameter("charset"); if (charset != null) { ((SOAPPartImpl) getSOAPPart()).setSourceCharsetEncoding(charset); if(!charset.equalsIgnoreCase("utf-8")) setProperty(CHARACTER_SET_ENCODING, charset); } } public void setLazyAttachments(boolean flag) { lazyAttachments = flag; } }