blob: f147a25722e9185c3c2a4906b7f45029cbb5e873 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.axis2.transport;
import org.apache.axiom.attachments.Attachments;
import org.apache.axiom.attachments.CachedFileDataSource;
import org.apache.axiom.attachments.lifecycle.LifecycleManager;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axiom.om.OMXMLParserWrapper;
import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axiom.soap.SOAP12Constants;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.SOAPModelBuilder;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.builder.Builder;
import org.apache.axis2.builder.BuilderUtil;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.deployment.DeploymentConstants;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.i18n.Messages;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.util.JavaUtils;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.activation.DataSource;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public class TransportUtils {
private static final Log log = LogFactory.getLog(TransportUtils.class);
public static SOAPEnvelope createSOAPMessage(MessageContext msgContext) throws AxisFault {
return createSOAPMessage(msgContext, false);
}
/**
* This method will create a SOAPEnvelope based on the InputStream stored on
* the MessageContext. The 'detach' parameter controls whether or not the
* underlying DetachableInputStream is detached at the end of the method. Note,
* detaching the DetachableInputStream closes the underlying InputStream that
* is stored on the MessageContext.
*/
public static SOAPEnvelope createSOAPMessage(MessageContext msgContext,
boolean detach) throws AxisFault {
// final SOAPEnvelope envelope = msgContext.getEnvelope();
// if (envelope != null) {
// if (envelope.isComplete())
// return envelope;
// }
try {
InputStream inStream = (InputStream) msgContext
.getProperty(MessageContext.TRANSPORT_IN);
msgContext.setProperty(MessageContext.TRANSPORT_IN, null);
// this inputstram is set by the TransportSender represents a two
// way transport or a Transport Recevier
if (inStream == null) {
throw new AxisFault(Messages.getMessage("inputstreamNull"));
}
String contentType = (String) msgContext
.getProperty(Constants.Configuration.CONTENT_TYPE);
// get the type of char encoding
String charSetEnc = (String) msgContext
.getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
if (charSetEnc == null && contentType != null) {
charSetEnc = BuilderUtil.getCharSetEncoding(contentType);
} else if (charSetEnc == null) {
charSetEnc = MessageContext.DEFAULT_CHAR_SET_ENCODING;
}
msgContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, charSetEnc);
SOAPEnvelope env = createSOAPMessage(msgContext, inStream, contentType);
if(detach) {
detachInputStream(msgContext);
}
return env;
} catch (Exception e) {
throw AxisFault.makeFault(e);
}
}
/**
* Objective of this method is to capture the SOAPEnvelope creation logic
* and make it a common for all the transports and to in/out flows.
*
* @param msgContext message context
* @param inStream input stream
* @param contentType content type of the message
* @return the SOAPEnvelope
* @throws AxisFault if an error occurs
* @throws OMException if the xml is invalid
* @throws XMLStreamException if the stream is invalid
* @throws FactoryConfigurationError
*/
public static SOAPEnvelope createSOAPMessage(MessageContext msgContext,
InputStream inStream,
String contentType)
throws AxisFault, OMException, XMLStreamException,
FactoryConfigurationError {
OMElement documentElement = createDocumentElement(contentType, msgContext, inStream);
return createSOAPEnvelope(documentElement);
}
/**
* Objective of this method is to capture the SOAPEnvelope creation logic
* and make it a common for all the transports and to in/out flows.
*
* @param msgContext message context
* @param inStream input stream
* @param contentType content type of the message
* @param builder the builder to be used
* @return the SOAPEnvelope
* @throws AxisFault if an error occurs
* @throws OMException if the xml is invalid
* @throws XMLStreamException if the stream is invalid
* @throws FactoryConfigurationError
*/
public static SOAPEnvelope createSOAPMessage(MessageContext msgContext,
InputStream inStream,
String contentType,
Builder builder)
throws AxisFault, OMException, XMLStreamException, FactoryConfigurationError {
OMElement documentElement = createDocumentElement(contentType, builder,
msgContext, inStream);
return createSOAPEnvelope(documentElement);
}
public static SOAPEnvelope createSOAPEnvelope(OMElement documentElement) {
SOAPEnvelope envelope;
// Check whether we have received a SOAPEnvelope or not
if (documentElement instanceof SOAPEnvelope) {
envelope = (SOAPEnvelope) documentElement;
} else {
// If it is not a SOAPEnvelope we wrap that with a fake
// SOAPEnvelope.
SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory();
envelope = soapFactory.getDefaultEnvelope();
if (documentElement != null) {
envelope.getBody().addChild(documentElement);
}
}
return envelope;
}
public static OMElement createDocumentElement(String contentType,
MessageContext msgContext,
InputStream inStream)
throws AxisFault, XMLStreamException {
OMElement documentElement = null;
String type = null;
if (contentType != null) {
type = getContentType(contentType, msgContext);
Builder builder = MessageProcessorSelector.getMessageBuilder(type, msgContext);
if (builder != null) {
if (log.isDebugEnabled()) {
log.debug("createSOAPEnvelope using Builder (" +
builder.getClass() + ") selected from type (" + type +")");
}
documentElement = builder.processDocument(inStream, contentType, msgContext);
}
}
if (documentElement == null) {
documentElement = createDefaultDocumentElement(msgContext, inStream, type);
}
return documentElement;
}
private static OMElement createDefaultDocumentElement(MessageContext msgContext,
InputStream inStream, String type) {
OMElement documentElement;
if (msgContext.isDoingREST()) {
if (log.isDebugEnabled()) {
log.debug("Could not find a Builder for type (" + type + "). Using REST.");
}
OMXMLParserWrapper builder = BuilderUtil.createPOXBuilder(inStream, null);
documentElement = builder.getDocumentElement();
} else {
// FIXME making soap defualt for the moment..might effect the
// performance
if (log.isDebugEnabled()) {
log.debug("Could not find a Builder for type (" + type + "). Using SOAP.");
}
String charSetEnc = (String) msgContext
.getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
SOAPModelBuilder builder = BuilderUtil.createSOAPModelBuilder(inStream, charSetEnc);
documentElement = builder.getDocumentElement();
}
return documentElement;
}
public static OMElement createDocumentElement(String contentType, Builder builder,
MessageContext msgContext,
InputStream inStream)
throws AxisFault, XMLStreamException {
OMElement documentElement = null;
String type = null;
if (contentType != null) {
type = getContentType(contentType, msgContext);
if (builder != null) {
if (log.isDebugEnabled()) {
log.debug("createSOAPEnvelope using Builder (" +
builder.getClass() + ") selected from type (" + type +")");
}
documentElement = builder.processDocument(inStream, contentType, msgContext);
}
}
if (documentElement == null) {
documentElement = createDefaultDocumentElement(msgContext, inStream, type);
}
return documentElement;
}
public static String getContentType(String contentType, MessageContext msgContext) {
String type;
int index = contentType.indexOf(';');
if (index > 0) {
type = contentType.substring(0, index);
} else {
type = contentType;
}
// Some services send REST responces as text/xml. We should convert it to
// application/xml if its a REST response, if not it will try to use the SOAPMessageBuilder.
// isDoingREST should already be properly set by HTTPTransportUtils.initializeMessageContext
if (msgContext.isDoingREST() && HTTPConstants.MEDIA_TYPE_TEXT_XML.equals(type)) {
if (msgContext.isServerSide()) {
if (msgContext.getSoapAction() == null) {
// TODO - remove this logic.
//type = HTTPConstants.MEDIA_TYPE_APPLICATION_XML;
}
} else if (!msgContext.isPropertyTrue(Constants.Configuration.SOAP_RESPONSE_MEP)) {
type = HTTPConstants.MEDIA_TYPE_APPLICATION_XML;
}
}
return type;
}
/**
* Extracts and returns the character set encoding from the
* Content-type header
* Example:
* Content-Type: text/xml; charset=utf-8
*
* @param contentType
*/
public static String getCharSetEncoding(String contentType) {
if (log.isDebugEnabled()) {
log.debug("Input contentType (" + contentType + ")");
}
int index = contentType.indexOf(HTTPConstants.CHAR_SET_ENCODING);
if (index == -1) { // Charset encoding not found in the content-type header
// Using the default UTF-8
if (log.isDebugEnabled()) {
log.debug("CharSetEncoding defaulted (" + MessageContext.DEFAULT_CHAR_SET_ENCODING + ")");
}
return MessageContext.DEFAULT_CHAR_SET_ENCODING;
}
// If there are spaces around the '=' sign
int indexOfEq = contentType.indexOf("=", index);
// There can be situations where "charset" is not the last parameter of the Content-Type header
int indexOfSemiColon = contentType.indexOf(";", indexOfEq);
String value;
if (indexOfSemiColon > 0) {
value = (contentType.substring(indexOfEq + 1, indexOfSemiColon));
} else {
value = (contentType.substring(indexOfEq + 1, contentType.length())).trim();
}
// There might be "" around the value - if so remove them
if (value.indexOf('\"') != -1) {
value = value.replaceAll("\"", "");
}
value = value.trim();
if (log.isDebugEnabled()) {
log.debug("CharSetEncoding from content-type (" + value + ")");
}
return value;
}
public static void writeMessage(MessageContext msgContext, OutputStream out) throws AxisFault {
SOAPEnvelope envelope = msgContext.getEnvelope();
OMElement outputMessage = envelope;
if ((envelope != null) && msgContext.isDoingREST()) {
outputMessage = envelope.getBody().getFirstElement();
}
if (outputMessage != null) {
try {
OMOutputFormat format = new OMOutputFormat();
// Pick the char set encoding from the msgContext
String charSetEnc =
(String) msgContext
.getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
format.setDoOptimize(false);
format.setDoingSWA(false);
format.setCharSetEncoding(charSetEnc);
outputMessage.serializeAndConsume(out, format);
out.flush();
} catch (Exception e) {
throw AxisFault.makeFault(e);
}
} else {
throw new AxisFault(Messages.getMessage("outMessageNull"));
}
}
/**
* @param contentType The contentType of the incoming message. It may be null
* @param defaultSOAPNamespace Usually set the version that is expected. This a fallback if the contentType is unavailable or
* does not match our expectations
* @return null or the soap namespace. A null indicates that the message will be interpretted as a non-SOAP (i.e. REST) message
*/
private static String getSOAPNamespaceFromContentType(String contentType,
String defaultSOAPNamespace) {
String returnNS = defaultSOAPNamespace;
// Discriminate using the content Type
if (contentType != null) {
/*
* SOAP11 content-type is "text/xml"
* SOAP12 content-type is "application/soap+xml"
*
* What about other content-types?
*
* TODO: I'm not fully convinced this method is complete, given the media types
* listed in HTTPConstants. Should we assume all application/* is SOAP12?
* Should we assume all text/* is SOAP11?
*
* So, we'll follow this pattern:
* 1) find the content-type main setting
* 2) if (1) not understood, find the "type=" param
* Thilina: I merged (1) & (2)
*/
if (JavaUtils.indexOfIgnoreCase(contentType, SOAP12Constants.SOAP_12_CONTENT_TYPE) > -1)
{
returnNS = SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI;
}
// search for "type=text/xml"
else
if (JavaUtils.indexOfIgnoreCase(contentType, SOAP11Constants.SOAP_11_CONTENT_TYPE) > -1)
{
returnNS = SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI;
}
}
if (returnNS == null) {
if (log.isDebugEnabled()) {
log.debug("No content-type or \"type=\" parameter was found in the content-type " +
"header and no default was specified, thus defaulting to SOAP 1.1.");
}
returnNS = SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI;
}
if (log.isDebugEnabled()) {
log.debug("content-type: " + contentType);
log.debug("defaultSOAPNamespace: " + defaultSOAPNamespace);
log.debug("Returned namespace: " + returnNS);
}
return returnNS;
}
public static void processContentTypeForAction(String contentType, MessageContext msgContext) {
//Check for action header and set it in as soapAction in MessageContext
int index = contentType.indexOf("action");
if (index > -1) {
String transientString = contentType.substring(index, contentType.length());
int equal = transientString.indexOf("=");
int firstSemiColon = transientString.indexOf(";");
String soapAction; // This will contain "" in the string
if (firstSemiColon > -1) {
soapAction = transientString.substring(equal + 1, firstSemiColon);
} else {
soapAction = transientString.substring(equal + 1, transientString.length());
}
if ((soapAction != null) && soapAction.startsWith("\"")
&& soapAction.endsWith("\"")) {
soapAction = soapAction
.substring(1, soapAction.length() - 1);
}
msgContext.setSoapAction(soapAction);
}
}
/**
* This is a helper method to get the response written flag from the RequestResponseTransport
* instance.
*/
public static boolean isResponseWritten(MessageContext messageContext) {
RequestResponseTransport reqResTransport = getRequestResponseTransport(messageContext);
if (reqResTransport != null) {
if (log.isDebugEnabled()) {
log.debug("Found RequestResponseTransport returning isResponseWritten()");
}
return reqResTransport.isResponseWritten();
} else {
if (log.isDebugEnabled()) {
log.debug("Did not find RequestResponseTransport returning false from get"
+ "ResponseWritten()");
}
return false;
}
}
/**
* This is a helper method to set the response written flag on the RequestResponseTransport
* instance.
*/
public static void setResponseWritten(MessageContext messageContext, boolean responseWritten) {
RequestResponseTransport reqResTransport = getRequestResponseTransport(messageContext);
if (reqResTransport != null) {
if (log.isDebugEnabled()) {
log.debug("Found RequestResponseTransport setting response written");
}
reqResTransport.setResponseWritten(responseWritten);
} else {
if (log.isDebugEnabled()) {
log.debug("Did not find RequestResponseTransport cannot set response written");
}
}
}
/**
* This is an internal helper method to retrieve the RequestResponseTransport instance
* from the MessageContext object. The MessageContext may be the response MessageContext so
* in that case we will have to retrieve the request MessageContext from the OperationContext.
*/
private static RequestResponseTransport getRequestResponseTransport(MessageContext messageContext) {
try {
// If this is the request MessageContext we should find it directly by the getProperty()
// method
RequestResponseTransport transportControl = (RequestResponseTransport)
messageContext.getProperty(RequestResponseTransport.TRANSPORT_CONTROL);
if (transportControl != null) {
return transportControl;
}
// If this is the response MessageContext we need to look for the request MessageContext
else if (messageContext.getOperationContext() != null
&& messageContext.getOperationContext().getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE) != null) {
return (RequestResponseTransport) messageContext.getOperationContext().getMessageContext(
WSDLConstants.MESSAGE_LABEL_IN_VALUE).getProperty(RequestResponseTransport.TRANSPORT_CONTROL);
}
else {
return null;
}
}
catch(AxisFault af) {
// probably should not be fatal, so just log the message
String msg = Messages.getMessage("getMessageContextError", af.toString());
log.debug(msg);
return null;
}
}
/**
* Clean up cached attachment file
* @param msgContext
*/
public static void deleteAttachments(MessageContext msgContext) {
if (log.isDebugEnabled()) {
log.debug("Entering deleteAttachments()");
}
Attachments attachments = msgContext.getAttachmentMap();
if (attachments != null) {
// Get the list of Content IDs for the attachments...but does not try to pull the stream for new attachments.
// (Pulling the stream for new attachments will probably fail...the stream is probably closed)
List keys = attachments.getContentIDList();
if (keys != null && keys.size() > 0) {
String key = null;
File file = null;
LifecycleManager lcm = (LifecycleManager)msgContext.getRootContext().getAxisConfiguration().getParameterValue(DeploymentConstants.ATTACHMENTS_LIFECYCLE_MANAGER);
DataSource dataSource = null;
for (int i = 0; i < keys.size(); i++) {
try {
key = (String) keys.get(i);
dataSource = attachments.getDataHandler(key).getDataSource();
if(dataSource instanceof CachedFileDataSource){
file = ((CachedFileDataSource)dataSource).getFile();
if (log.isDebugEnabled()) {
log.debug("Delete cache attachment file: "+file.getName());
}
if(lcm!=null){
if(log.isDebugEnabled()){
log.debug("deleting file using lifecyclemanager");
}
lcm.delete(file);
}else{
file.delete();
}
}
}
catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Delete cache attachment file failed"+ e.getMessage());
}
if (file != null) {
if(lcm!=null){
try{
lcm.deleteOnExit(file);
}catch(Exception ex){
file.deleteOnExit();
}
}
else{
file.deleteOnExit();
}
}
}
}
}
}
if (log.isDebugEnabled()) {
log.debug("Exiting deleteAttachments()");
}
}
/**
* Prepare the message in the given message context so that the underlying input stream can be
* closed.
*
* @param msgContext
*/
public static void detachInputStream(MessageContext msgContext) throws AxisFault {
if (msgContext != null) {
OMXMLParserWrapper builder = (OMXMLParserWrapper)msgContext.getProperty(Constants.BUILDER);
if (builder != null) {
builder.detach();
} else {
Attachments attachments = msgContext.getAttachmentMap(false);
if (attachments != null) {
attachments.getAllContentIDs();
} else {
SOAPEnvelope envelope = msgContext.getEnvelope();
if (envelope != null) {
envelope.build();
}
}
}
}
}
/**
* <p>
* Checks whether MTOM needs to be enabled for the message represented by
* the msgContext. We check value assigned to the "enableMTOM" property
* either using the config files (axis2.xml, services.xml) or
* programatically. Programatic configuration is given priority. If the
* given value is "optional", MTOM will be enabled only if the incoming
* message was an MTOM message.
* </p>
*
* @param msgContext the active MessageContext
* @return true if SwA needs to be enabled
*/
public static boolean doWriteMTOM(MessageContext msgContext) {
boolean enableMTOM;
Object enableMTOMObject = null;
// First check the whether MTOM is enabled by the configuration
// (Eg:Axis2.xml, services.xml)
Parameter parameter = msgContext.getParameter(Constants.Configuration.ENABLE_MTOM);
if (parameter != null) {
enableMTOMObject = parameter.getValue();
}
// Check whether the configuration is overridden programatically..
// Priority given to programatically setting of the value
Object property = msgContext.getProperty(Constants.Configuration.ENABLE_MTOM);
if (property != null) {
enableMTOMObject = property;
}
enableMTOM = JavaUtils.isTrueExplicitly(enableMTOMObject);
// Handle the optional value for enableMTOM
// If the value for 'enableMTOM' is given as optional and if the request
// message was a MTOM message we sent out MTOM
if (!enableMTOM && msgContext.isDoingMTOM() && (enableMTOMObject instanceof String)) {
if (((String) enableMTOMObject).equalsIgnoreCase(Constants.VALUE_OPTIONAL)) {
//In server side, we check whether request was MTOM
if (msgContext.isServerSide()) {
if (msgContext.isDoingMTOM()) {
enableMTOM = true;
}
// in the client side, we enable MTOM if it is optional
} else {
enableMTOM = true;
}
}
}
return enableMTOM;
}
/**
* <p>
* Checks whether SOAP With Attachments (SwA) needs to be enabled for the
* message represented by the msgContext. We check value assigned to the
* "enableSwA" property either using the config files (axis2.xml,
* services.xml) or programatically. Programatic configuration is given
* priority. If the given value is "optional", SwA will be enabled only if
* the incoming message was SwA type.
* </p>
*
* @param msgContext the active MessageContext
* @return true if SwA needs to be enabled
*/
public static boolean doWriteSwA(MessageContext msgContext) {
boolean enableSwA;
Object enableSwAObject = null;
// First check the whether SwA is enabled by the configuration
// (Eg:Axis2.xml, services.xml)
Parameter parameter = msgContext.getParameter(Constants.Configuration.ENABLE_SWA);
if (parameter != null) {
enableSwAObject = parameter.getValue();
}
// Check whether the configuration is overridden programatically..
// Priority given to programatically setting of the value
Object property = msgContext.getProperty(Constants.Configuration.ENABLE_SWA);
if (property != null) {
enableSwAObject = property;
}
enableSwA = JavaUtils.isTrueExplicitly(enableSwAObject);
// Handle the optional value for enableSwA
// If the value for 'enableSwA' is given as optional and if the request
// message was a SwA message we sent out SwA
if (!enableSwA && msgContext.isDoingSwA() && (enableSwAObject instanceof String)) {
if (((String) enableSwAObject).equalsIgnoreCase(Constants.VALUE_OPTIONAL)) {
enableSwA = true;
}
}
return enableSwA;
}
public static boolean isDoingREST(MessageContext msgContext) {
boolean enableREST = false;
// check whether isDoingRest is already true in the message context
if (msgContext.isDoingREST()) {
return true;
}
Object enableRESTProperty = msgContext.getProperty(Constants.Configuration.ENABLE_REST);
if (enableRESTProperty != null) {
enableREST = JavaUtils.isTrueExplicitly(enableRESTProperty);
}
msgContext.setDoingREST(enableREST);
return enableREST;
}
/**
* Utility method to query CharSetEncoding. First look in the
* MessageContext. If it's not there look in the OpContext. Use the defualt,
* if it's not given in either contexts.
*
* @param msgContext the active MessageContext
* @return String the CharSetEncoding
*/
public static String getCharSetEncoding(MessageContext msgContext) {
String charSetEnc = (String) msgContext
.getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
if (charSetEnc == null) {
OperationContext opctx = msgContext.getOperationContext();
if (opctx != null) {
charSetEnc = (String) opctx
.getProperty(Constants.Configuration.CHARACTER_SET_ENCODING);
}
/**
* If the char set enc is still not found use the default
*/
if (charSetEnc == null) {
charSetEnc = MessageContext.DEFAULT_CHAR_SET_ENCODING;
}
}
return charSetEnc;
}
}