com.sun.xml.messaging.saaj.soap.MessageImpl Maven / Gradle / Ivy
Show all versions of saaj-impl Show documentation
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package com.sun.xml.messaging.saaj.soap;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.activation.DataHandler;
import jakarta.activation.DataSource;
import jakarta.xml.soap.*;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import com.sun.xml.messaging.saaj.packaging.mime.Header;
import com.sun.xml.messaging.saaj.packaging.mime.internet.*;
import com.sun.xml.messaging.saaj.packaging.mime.util.*;
import com.sun.xml.messaging.saaj.packaging.mime.MessagingException;
import com.sun.xml.messaging.saaj.SOAPExceptionImpl;
import com.sun.xml.messaging.saaj.soap.impl.EnvelopeImpl;
import com.sun.xml.messaging.saaj.util.*;
import 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 ([email protected])
* @author Rajiv Mordani ([email protected])
* @author Manveen Kaur ([email protected])
*/
public abstract class MessageImpl
extends SOAPMessage
implements SOAPConstants {
public static final String CONTENT_ID = "Content-ID";
public static final String CONTENT_LOCATION = "Content-Location";
private static final Logger log =
Logger.getLogger(LogDomainConstants.SOAP_DOMAIN,
"com.sun.xml.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 attachments;
protected boolean saved = false;
protected byte[] messageBytes;
protected int messageByteCount;
protected Map 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
*/
protected boolean isFastInfoset = false;
/**
* 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;
private static Integer soapBodyPartSizeLimit;
public static final String SAAJ_MIME_SOAP_BODY_PART_SIZE_LIMIT = "saaj.mime.soapBodyPartSizeLimit";
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");
soapBodyPartSizeLimit = SAAJUtil.getSystemInteger(SAAJ_MIME_SOAP_BODY_PART_SIZE_LIMIT);
}
//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.
/**
* @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");
}
/**
* @param type
* must be all lower case
*/
private static boolean isEqualToSoap1_1Type(String type) {
return type.startsWith("text/xml") ||
type.startsWith("application/fastinfoset");
}
/**
* @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"));
}
/**
* @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.
*/
protected MessageImpl() {
this(false, false);
attachmentsInitialized = true;
}
/**
* 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.
*
* @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;
}
/**
* @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.
* @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.
* @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.
*
* @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);
}
protected 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().replaceAll("[\r\n]",""),
getExpectedContentType().replaceAll("[\r\n]","")});
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);
if (soapBodyPartSizeLimit != null && soapMessagePart.getSize() > soapBodyPartSizeLimit) {
throw new SOAPExceptionImpl("SOAP body part of size " + soapMessagePart.getSize() + " exceeded size limitation: " + soapBodyPartSizeLimit);
}
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.replaceAll("[\r\n]","")});
throw new SOAPExceptionImpl(
"Bad Content-Type for SOAP Part : " +
baseType);
}
SOAPPart soapPart = getSOAPPart();
setMimeHeaders(soapPart, soapMessagePart);
soapPart.setContent(isFastInfoset ?
FastInfosetReflection.FastInfosetSource_new(
soapPartInputStream) :
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;
} else {
return Boolean.parseBoolean(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().replaceAll("[\r\n]",""), "jakarta.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().replaceAll("[\r\n]",""), "jakarta.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)
*
* @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".
*/
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", convertToSingleLine(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", convertToSingleLine(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", convertToSingleLine(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", convertToSingleLine(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.
*/
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 nullIter = Collections.emptyIterator();
@Override
public Iterator 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", convertToSingleLine(ct.toString()));
}
private class MimeMatchingIterator implements Iterator {
public MimeMatchingIterator(MimeHeaders headers) {
this.headers = headers;
this.iter = attachments.iterator();
}
private Iterator 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 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 it = new MimeMatchingIterator(headers);
while (it.hasNext()) {
int index = attachments.indexOf(it.next());
attachments.set(index, null);
}
FinalArrayList f = new FinalArrayList<>();
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 : i.next();
} else {
// try content-location
MimeHeaders headersToMatch = new MimeHeaders();
headersToMatch.addHeader(CONTENT_LOCATION, uri);
Iterator i = this.getAttachments(headersToMatch);
_part = (i == null) ? null : i.next();
}
// try auto-generated JAXRPC CID
if (_part == null) {
Iterator j = this.getAttachments();
while (j.hasNext()) {
AttachmentPart p = 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.replaceAll("[\r\n]","")});
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 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<>();
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