blob: 9f6e49edc4fde4e2d6a3d0690b04a0dbdad11c8a [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.saaj;
import org.apache.axiom.attachments.Attachments;
import org.apache.axiom.attachments.ConfigurableDataHandler;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.impl.MTOMConstants;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.OperationClient;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.engine.DispatchPhase;
import org.apache.axis2.engine.Phase;
import org.apache.axis2.saaj.util.IDGenerator;
import org.apache.axis2.saaj.util.SAAJUtil;
import org.apache.axis2.saaj.util.UnderstandAllHeadersHandler;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.wsdl.WSDLConstants;
import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeader;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
*
*/
public class SOAPConnectionImpl extends SOAPConnection {
/** Attribute which keeps track of whether this connection has been closed */
private boolean closed = false;
private final ConfigurationContext configurationContext;
SOAPConnectionImpl() throws SOAPException {
// Create a new ConfigurationContext that will be used by all ServiceClient instances.
// There are two reasons why this is necessary:
// * Starting with r921685, if no ConfigurationContext is supplied to the ServiceClient,
// it will create a new one (unless it can locate one using MessageContext.getCurrentMessageContext(),
// but this is not the most common use case for SOAPConnection). This means that
// SOAPConnection#call would create a new ConfigurationContext every time, and this is
// too expensive.
// * We need to disable mustUnderstand processing. However, we can't do that on an AxisConfiguration
// that is shared with other components, because this would lead to unpredictable results.
// Note that we could also use a single ServiceClient instance, but then the SOAPConnection
// implementation would no longer be thread safe. Although thread safety is not explicitly required
// by the SAAJ specs, it appears that the SOAPConnection in Sun's reference implementation is
// thread safe.
try {
configurationContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null);
disableMustUnderstandProcessing(configurationContext.getAxisConfiguration());
} catch (AxisFault ex) {
throw new SOAPException(ex);
}
}
/**
* Sends the given message to the specified endpoint and blocks until it has returned the
* response.
*
* @param request the <CODE>SOAPMessage</CODE> object to be sent
* @param endpoint an <code>Object</code> that identifies where the message should be sent. It
* is required to support Objects of type <code>java.lang.String</code>,
* <code>java.net.URL</code>, and when JAXM is present <code>javax.xml.messaging.URLEndpoint</code>
* @return the <CODE>SOAPMessage</CODE> object that is the response to the message that was
* sent
* @throws javax.xml.soap.SOAPException if there is a SOAP error, or this SOAPConnection is
* already closed
*/
public SOAPMessage call(SOAPMessage request, Object endpoint) throws SOAPException {
if (closed) {
throw new SOAPException("SOAPConnection closed");
}
// initialize URL
URL url;
try {
url = (endpoint instanceof URL) ? (URL)endpoint : new URL(endpoint.toString());
} catch (MalformedURLException e) {
throw new SOAPException(e.getMessage());
}
// initialize and set Options
Options options = new Options();
options.setTo(new EndpointReference(url.toString()));
// initialize the Sender
ServiceClient serviceClient;
OperationClient opClient;
try {
serviceClient = new ServiceClient(configurationContext, null);
opClient = serviceClient.createClient(ServiceClient.ANON_OUT_IN_OP);
} catch (AxisFault e) {
throw new SOAPException(e);
}
options.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING,
request.getProperty(SOAPMessage.CHARACTER_SET_ENCODING));
opClient.setOptions(options);
MessageContext requestMsgCtx = new MessageContext();
org.apache.axiom.soap.SOAPEnvelope envelope;
if (isMTOM(request)) {
envelope = SAAJUtil.toOMSOAPEnvelope(request);
options.setProperty(Constants.Configuration.ENABLE_MTOM, Constants.VALUE_TRUE);
} else {
envelope = SAAJUtil.toOMSOAPEnvelope(request.getSOAPPart().getDocumentElement());
if (request.countAttachments() != 0) { // SOAPMessage with attachments
Attachments attachments = requestMsgCtx.getAttachmentMap();
for (Iterator it = request.getAttachments(); it.hasNext(); ) {
AttachmentPart attachment = (AttachmentPart)it.next();
String contentId = attachment.getContentId();
// Axiom currently doesn't support attachments without Content-ID
// (see WSCOMMONS-418); generate one if necessary.
if (contentId == null) {
contentId = IDGenerator.generateID();
}
DataHandler handler = attachment.getDataHandler();
// make sure that AttachmentPart content-type overrides DataHandler content-type
if (!SAAJUtil.compareContentTypes(attachment.getContentType(), handler.getContentType())) {
ConfigurableDataHandler configuredHandler = new ConfigurableDataHandler(handler.getDataSource());
configuredHandler.setContentType(attachment.getContentType());
handler = configuredHandler;
}
attachments.addDataHandler(contentId, handler);
}
options.setProperty(Constants.Configuration.ENABLE_SWA, Constants.VALUE_TRUE);
}
}
Map<String,String> httpHeaders = null;
for (Iterator it = request.getMimeHeaders().getAllHeaders(); it.hasNext(); ) {
MimeHeader header = (MimeHeader)it.next();
String name = header.getName().toLowerCase();
if (name.equals("soapaction")) {
requestMsgCtx.setSoapAction(header.getValue());
} else if (name.equals("content-type")) {
// Don't set the Content-Type explicitly since it will be computed by the
// message builder.
} else {
if (httpHeaders == null) {
httpHeaders = new HashMap<String,String>();
}
httpHeaders.put(header.getName(), header.getValue());
}
}
if (httpHeaders != null) {
requestMsgCtx.setProperty(HTTPConstants.HTTP_HEADERS, httpHeaders);
}
try {
MessageContext responseMsgCtx;
try {
requestMsgCtx.setEnvelope(envelope);
opClient.addMessageContext(requestMsgCtx);
opClient.execute(true);
responseMsgCtx =
opClient.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
} catch (AxisFault ex) {
throw new SOAPException(ex.getMessage(), ex);
}
SOAPMessage response = getSOAPMessage(responseMsgCtx.getEnvelope());
Attachments attachments = responseMsgCtx.getAttachmentMap();
for (String contentId : attachments.getAllContentIDs()) {
if (!contentId.equals(attachments.getRootPartContentID())) {
AttachmentPart ap = response.createAttachmentPart(
attachments.getDataHandler(contentId));
ap.setContentId(contentId);
response.addAttachmentPart(ap);
}
}
return response;
} finally {
try {
serviceClient.cleanupTransport();
serviceClient.cleanup();
} catch (AxisFault ex) {
throw new SOAPException(ex);
}
}
}
private static boolean isMTOM(SOAPMessage soapMessage) {
SOAPPart soapPart = soapMessage.getSOAPPart();
String[] contentTypes = soapPart.getMimeHeader("Content-Type");
if (contentTypes != null && contentTypes.length > 0) {
return SAAJUtil.normalizeContentType(contentTypes[0]).equals("application/xop+xml");
} else {
return false;
}
}
/*
* Installs UnderstandAllHeadersHandler that marks all headers as processed
* because MU validation should not be done for SAAJ clients.
*/
private void disableMustUnderstandProcessing(AxisConfiguration config) {
DispatchPhase phase;
phase = getDispatchPhase(config.getInFlowPhases());
if (phase != null) {
phase.addHandler(new UnderstandAllHeadersHandler());
}
phase = getDispatchPhase(config.getInFaultFlowPhases());
if (phase != null) {
phase.addHandler(new UnderstandAllHeadersHandler());
}
}
private static DispatchPhase getDispatchPhase(List<Phase> phases) {
for (Phase phase : phases) {
if (phase instanceof DispatchPhase) {
return (DispatchPhase)phase;
}
}
return null;
}
/**
* Closes this <CODE>SOAPConnection</CODE> object.
*
* @throws javax.xml.soap.SOAPException if there is a SOAP error, or this SOAPConnection is
* already closed
*/
public void close() throws SOAPException {
if (closed) {
throw new SOAPException("SOAPConnection Closed");
}
try {
configurationContext.terminate();
} catch (AxisFault axisFault) {
throw new SOAPException(axisFault.getMessage());
}
closed = true;
}
/**
* This method handles the conversion of an OM SOAP Envelope to a SAAJ SOAPMessage
*
* @param respOMSoapEnv
* @return the SAAJ SOAPMessage
* @throws SOAPException If an exception occurs during this conversion
*/
private SOAPMessage getSOAPMessage(org.apache.axiom.soap.SOAPEnvelope respOMSoapEnv)
throws SOAPException {
// Create the basic SOAP Message
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage response = mf.createMessage();
SOAPPart sPart = response.getSOAPPart();
javax.xml.soap.SOAPEnvelope env = sPart.getEnvelope();
SOAPBody body = env.getBody();
SOAPHeader header = env.getHeader();
// Convert all header blocks
org.apache.axiom.soap.SOAPHeader header2 = respOMSoapEnv.getHeader();
if (header2 != null) {
for (Iterator hbIter = header2.examineAllHeaderBlocks(); hbIter.hasNext();) {
// Converting a single OM SOAP HeaderBlock to a SAAJ SOAP
// HeaderBlock
org.apache.axiom.soap.SOAPHeaderBlock hb = (org.apache.axiom.soap.SOAPHeaderBlock)
hbIter.next();
final QName hbQName = hb.getQName();
final SOAPHeaderElement headerEle = header.addHeaderElement(env.createName(hbQName
.getLocalPart(), hbQName.getPrefix(), hbQName.getNamespaceURI()));
for (Iterator attribIter = hb.getAllAttributes(); attribIter.hasNext();) {
OMAttribute attr = (OMAttribute)attribIter.next();
final QName attrQName = attr.getQName();
headerEle.addAttribute(env.createName(attrQName.getLocalPart(), attrQName
.getPrefix(), attrQName.getNamespaceURI()), attr.getAttributeValue());
}
final String role = hb.getRole();
if (role != null) {
headerEle.setActor(role);
}
headerEle.setMustUnderstand(hb.getMustUnderstand());
toSAAJElement(headerEle, hb, response);
}
}
// Convert the body
toSAAJElement(body, respOMSoapEnv.getBody(), response);
return response;
}
/**
* Converts an OMNode into a SAAJ SOAPElement
*
* @param saajEle
* @param omNode
* @param saajSOAPMsg
* @throws SOAPException If conversion fails
*/
private void toSAAJElement(SOAPElement saajEle,
OMNode omNode,
javax.xml.soap.SOAPMessage saajSOAPMsg) throws SOAPException {
if (omNode instanceof OMText) {
return; // simply return since the text has already been added to saajEle
}
if (omNode instanceof OMElement) {
OMElement omEle = (OMElement)omNode;
for (Iterator childIter = omEle.getChildren(); childIter.hasNext();) {
OMNode omChildNode = (OMNode)childIter.next();
SOAPElement saajChildEle = null;
if (omChildNode instanceof OMText) {
// check whether the omtext refers to an attachment
final OMText omText = (OMText)omChildNode;
if (omText.isOptimized()) { // is this an attachment?
final DataHandler datahandler = (DataHandler)omText.getDataHandler();
AttachmentPart attachment = saajSOAPMsg.createAttachmentPart(datahandler);
final String id = IDGenerator.generateID();
attachment.setContentId("<" + id + ">");
attachment.setContentType(datahandler.getContentType());
saajSOAPMsg.addAttachmentPart(attachment);
SOAPElement xopInclude = saajEle.addChildElement(MTOMConstants.XOP_INCLUDE,
"xop", MTOMConstants.XOP_NAMESPACE_URI);
xopInclude.addAttribute(
saajSOAPMsg.getSOAPPart().getEnvelope().createName("href"),
"cid:" + id);
} else {
saajChildEle = saajEle.addTextNode(omText.getText());
}
} else if (omChildNode instanceof OMElement) {
OMElement omChildEle = (OMElement)omChildNode;
final QName omChildQName = omChildEle.getQName();
saajChildEle =
saajEle.addChildElement(omChildQName.getLocalPart(),
omChildQName.getPrefix(),
omChildQName.getNamespaceURI());
for (Iterator attribIter = omChildEle.getAllAttributes();
attribIter.hasNext();) {
OMAttribute attr = (OMAttribute)attribIter.next();
final QName attrQName = attr.getQName();
saajChildEle.addAttribute(saajSOAPMsg.getSOAPPart().getEnvelope().
createName(attrQName.getLocalPart(),
attrQName.getPrefix(),
attrQName.getNamespaceURI()),
attr.getAttributeValue());
}
}
// go down the tree adding child elements, till u reach a leaf(i.e. text element)
toSAAJElement(saajChildEle, omChildNode, saajSOAPMsg);
}
}
}
/** overrided SOAPConnection's get() method */
public SOAPMessage get(Object to) throws SOAPException {
URL url = null;
try {
url = (to instanceof URL) ? (URL)to : new URL(to.toString());
} catch (MalformedURLException e) {
throw new SOAPException(e);
}
int responseCode;
boolean isFailure = false;
HttpURLConnection httpCon = null;
try {
httpCon = (HttpURLConnection)url.openConnection();
httpCon.setDoOutput(true);
httpCon.setDoInput(true);
httpCon.setUseCaches(false);
httpCon.setRequestMethod("GET");
HttpURLConnection.setFollowRedirects(true);
httpCon.connect();
responseCode = httpCon.getResponseCode();
// 500 is allowed for SOAP faults
if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
isFailure = true;
} else if ((responseCode / 100) != 2) {
throw new SOAPException("Error response: (" + responseCode
+ httpCon.getResponseMessage());
}
} catch (IOException e) {
throw new SOAPException(e);
}
//Construct the soapmessage from http response
SOAPMessage soapMessage = null;
if (responseCode == HttpURLConnection.HTTP_OK) {
try {
//read http headers & load mimeheaders
MimeHeaders mimeHeaders = new MimeHeaders();
String key, value;
// skip status line
int i = 1;
while (true) {
key = httpCon.getHeaderFieldKey(i);
value = httpCon.getHeaderField(i);
if (key == null && value == null) {
break;
}
if (key != null) {
StringTokenizer values = new StringTokenizer(value, ",");
while (values.hasMoreTokens()) {
mimeHeaders.addHeader(key, values.nextToken().trim());
}
}
i++;
}
InputStream httpInputStream;
if (isFailure) {
httpInputStream = httpCon.getErrorStream();
} else {
httpInputStream = httpCon.getInputStream();
}
soapMessage = new SOAPMessageImpl(httpInputStream, mimeHeaders, false);
httpInputStream.close();
httpCon.disconnect();
} catch (SOAPException e) {
throw e;
} catch (Exception e) {
throw new SOAPException(e.getMessage());
}
}
return soapMessage;
}
}