/* * 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; } }