blob: 38cd9a55dca9741dfaba464859a39db669d6ca87 [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.ode.axis2.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.w3c.dom.Document;
import javax.wsdl.Binding;
import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.BindingOutput;
import javax.wsdl.Definition;
import javax.wsdl.Fault;
import javax.wsdl.Input;
import javax.wsdl.Message;
import javax.wsdl.Operation;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.extensions.ElementExtensible;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.extensions.soap.SOAPBody;
import javax.wsdl.extensions.soap.SOAPHeader;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.xml.namespace.QName;
import java.util.*;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.SOAPFault;
import org.apache.axiom.soap.SOAPFaultCode;
import org.apache.axiom.soap.SOAPFaultDetail;
import org.apache.axiom.soap.SOAPFaultReason;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.namespace.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.ode.axis2.OdeFault;
import org.apache.ode.il.OMUtils;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.Namespaces;
import org.apache.ode.utils.wsdl.Messages;
import org.apache.ode.utils.wsdl.WsdlUtils;
import org.apache.ode.utils.stl.CollectionsX;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* SOAP/ODE Message converter. Uses WSDL binding information to convert the protocol-neutral ODE representation into a SOAP
* representation and vice versa.
*/
public class SoapMessageConverter {
private static final Messages __msgs = Messages.getMessages(Messages.class);
private static final Logger __log = LoggerFactory.getLogger(SoapMessageConverter.class);
SOAPFactory _soapFactory;
Definition _def;
QName _serviceName;
String _portName;
Service _serviceDef;
Binding _binding;
Port _port;
boolean _isRPC;
private SOAPBinding _soapBinding;
public SoapMessageConverter(Definition def, QName serviceName, String portName) throws AxisFault {
if (def == null)
throw new NullPointerException("No WSDL definition was found for service "
+ serviceName + " and port " + portName);
_def = def;
_serviceName = serviceName;
_portName = portName;
_serviceDef = _def.getService(serviceName);
if (_serviceDef == null)
throw new OdeFault(__msgs.msgServiceDefinitionNotFound(serviceName));
_port = _serviceDef.getPort(portName);
if (_port == null)
throw new OdeFault(__msgs.msgPortDefinitionNotFound(serviceName, portName));
_binding = _port.getBinding();
if (_binding == null)
throw new OdeFault(__msgs.msgBindingNotFound(portName));
try {
if (!WsdlUtils.useSOAPBinding(_port)) {
throw new OdeFault(__msgs.msgNoSOAPBindingForPort(_portName));
}
_soapBinding = (SOAPBinding) WsdlUtils.getBindingExtension(_port);
} catch (IllegalArgumentException iae) {
throw new OdeFault(iae);
}
String style = _soapBinding.getStyle();
_isRPC = style != null && style.equals("rpc");
if (_soapBinding.getElementType().getNamespaceURI().equals(Constants.URI_WSDL11_SOAP)) {
_soapFactory = OMAbstractFactory.getSOAP11Factory();
} else if (_soapBinding.getElementType().getNamespaceURI().equals(Constants.URI_WSDL12_SOAP)) {
_soapFactory = OMAbstractFactory.getSOAP12Factory();
} else {
throw new IllegalStateException("Unsupported SOAP binding: " + _soapBinding.getElementType());
}
}
@SuppressWarnings("unchecked")
public void createSoapRequest(MessageContext msgCtx, org.apache.ode.bpel.iapi.Message message, Operation op) throws AxisFault {
if (op == null)
throw new NullPointerException("Null operation");
// The message can be null if the input message has no part
if (op.getInput() != null && op.getInput().getMessage().getParts().size() > 0 && message == null)
throw new NullPointerException("Null message.");
if (msgCtx == null)
throw new NullPointerException("Null msgCtx");
BindingOperation bop = _binding.getBindingOperation(op.getName(), null, null);
if (bop == null)
throw new OdeFault(__msgs.msgBindingOperationNotFound(_serviceName, _portName, op.getName()));
BindingInput bi = bop.getBindingInput();
if (bi == null)
//throw new OdeFault(__msgs.msgBindingInputNotFound(_serviceName, _portName, op.getName()));
__log.warn("",__msgs.msgBindingInputNotFound(_serviceName, _portName, op.getName()));
SOAPEnvelope soapEnv = msgCtx.getEnvelope();
if (soapEnv == null) {
soapEnv = _soapFactory.getDefaultEnvelope();
msgCtx.setEnvelope(soapEnv);
}
Message inputMessage = (op.getInput() != null) ? op.getInput().getMessage() : null;
createSoapHeaders(soapEnv, getSOAPHeaders(bi), inputMessage, message.getHeaderParts());
SOAPBody soapBody = getSOAPBody(bi);
if (soapBody != null) {
org.apache.axiom.soap.SOAPBody sb = soapEnv.getBody() == null ? _soapFactory.createSOAPBody(soapEnv) : soapEnv.getBody();
createSoapBody(sb, soapBody, inputMessage, message.getMessage(), op.getName());
}
}
public void createSoapResponse(MessageContext msgCtx, org.apache.ode.bpel.iapi.Message message, Operation op) throws AxisFault {
if (op == null)
throw new NullPointerException("Null operation");
if (message == null)
throw new NullPointerException("Null message.");
if (msgCtx == null)
throw new NullPointerException("Null msgCtx");
BindingOperation bop = _binding.getBindingOperation(op.getName(),null,null);
if (bop == null)
throw new OdeFault(__msgs.msgBindingOperationNotFound(_serviceName, _portName, op.getName()));
BindingOutput bo = bop.getBindingOutput();
if (bo == null)
throw new OdeFault(__msgs.msgBindingOutputNotFound(_serviceName, _portName, op.getName()));
SOAPEnvelope soapEnv = msgCtx.getEnvelope();
if (soapEnv == null) {
soapEnv = _soapFactory.getDefaultEnvelope();
msgCtx.setEnvelope(soapEnv);
}
if (message.getHeaderParts().size() > 0 || getSOAPHeaders(bo).size() > 0)
createSoapHeaders(soapEnv, getSOAPHeaders(bo), op.getOutput().getMessage(), message.getHeaderParts());
SOAPBody soapBody = getSOAPBody(bo);
if (soapBody != null) {
org.apache.axiom.soap.SOAPBody sb = soapEnv.getBody() == null ? _soapFactory.createSOAPBody(soapEnv) : soapEnv.getBody();
createSoapBody(sb, soapBody, op.getOutput().getMessage(), message.getMessage(), op.getName() + "Response");
}
}
public void createSoapHeaders(SOAPEnvelope soapEnv, List<SOAPHeader> headerDefs, Message msgdef, Map<String,Node> headers) throws AxisFault {
if (msgdef == null)
return;
for (SOAPHeader sh : headerDefs) handleSoapHeaderDef(soapEnv, sh, msgdef, headers);
org.apache.axiom.soap.SOAPHeader soaphdr = soapEnv.getHeader();
if (soaphdr == null) soaphdr = _soapFactory.createSOAPHeader(soapEnv);
for (Node headerNode : headers.values())
if (headerNode.getNodeType() == Node.ELEMENT_NODE) {
if (soaphdr.getFirstChildWithName(new QName(headerNode.getNamespaceURI(), headerNode.getLocalName())) == null)
soaphdr.addChild(OMUtils.toOM((Element) headerNode, _soapFactory));
} else {
throw new OdeFault(__msgs.msgSoapHeaderMustBeAnElement(headerNode));
}
}
@SuppressWarnings("unchecked")
private void handleSoapHeaderDef(SOAPEnvelope soapEnv, SOAPHeader headerdef, Message msgdef, Map<String, Node> headers) throws AxisFault {
boolean payloadMessageHeader = headerdef.getMessage() == null || headerdef.getMessage().equals(msgdef.getQName());
if (headerdef.getPart() == null) return;
if (payloadMessageHeader && msgdef.getPart(headerdef.getPart()) == null)
throw new OdeFault(__msgs.msgSoapHeaderReferencesUnkownPart(headerdef.getPart()));
Element srcPartEl = null;
if (headers.size() > 0 && payloadMessageHeader){
try {
srcPartEl = (Element) headers.get(headerdef.getPart());
} catch (ClassCastException e) {
throw new OdeFault(__msgs.msgSoapHeaderMustBeAnElement(headers.get(headerdef.getPart())));
}
}
// We don't complain about missing header data unless they are part of the message payload. This is
// because AXIS may be providing these headers.
if (srcPartEl == null && payloadMessageHeader)
throw new OdeFault(__msgs.msgOdeMessageMissingRequiredPart(headerdef.getPart()));
if (srcPartEl == null) return;
org.apache.axiom.soap.SOAPHeader soaphdr = soapEnv.getHeader();
if (soaphdr == null) {
soaphdr = _soapFactory.createSOAPHeader(soapEnv);
}
OMElement omPart = OMUtils.toOM(srcPartEl, _soapFactory);
for (Iterator<OMNode> i = omPart.getChildren(); i.hasNext();) {
OMNode node = i.next();
i.remove();
soaphdr.addChild(node);
}
}
public SOAPFault createSoapFault(Element message, QName faultName, Operation op) throws AxisFault {
OMElement detail = buildSoapDetail(message, faultName, op);
SOAPFault fault = _soapFactory.createSOAPFault();
SOAPFaultCode code = _soapFactory.createSOAPFaultCode(fault);
code.setText(new QName(Namespaces.SOAP_ENV_NS, "Server"));
SOAPFaultReason reason = _soapFactory.createSOAPFaultReason(fault);
reason.setText(faultName);
SOAPFaultDetail soapDetail = _soapFactory.createSOAPFaultDetail(fault);
if (detail != null)
soapDetail.addDetailEntry(detail);
return fault;
}
private OMElement buildSoapDetail(Element message, QName faultName, Operation op) throws AxisFault {
if (faultName.getNamespaceURI() == null)
return toFaultDetail(faultName, message);
if (op == null) {
return toFaultDetail(faultName, message);
}
Fault f = op.getFault(faultName.getLocalPart());
if (f == null)
return toFaultDetail(faultName, message);
// For faults, there will be exactly one part.
Part p = (Part)f.getMessage().getParts().values().iterator().next();
if (p == null)
return toFaultDetail(faultName, message);
Element partEl= DOMUtils.findChildByName(message,new QName(null,p.getName()));
if (partEl == null)
return toFaultDetail(faultName, message);
Element detail = DOMUtils.findChildByName(partEl, p.getElementName());
if (detail == null)
return toFaultDetail(faultName, message);
return OMUtils.toOM(detail, _soapFactory);
}
private OMElement toFaultDetail(QName fault, Element message) {
if (message == null) return null;
Element firstPart = DOMUtils.getFirstChildElement(message);
if (firstPart == null) return null;
Element detail = DOMUtils.getFirstChildElement(firstPart);
if (detail == null) return OMUtils.toOM(firstPart, _soapFactory);
return OMUtils.toOM(detail, _soapFactory);
}
public void parseSoapRequest(org.apache.ode.bpel.iapi.Message odeMessage, SOAPEnvelope envelope, Operation op) throws AxisFault {
BindingOperation bop = _binding.getBindingOperation(op.getName(), null, null);
if (bop == null)
throw new OdeFault(__msgs.msgBindingOperationNotFound(_serviceName, _portName, op.getName()));
BindingInput bi = bop.getBindingInput();
if (bi == null)
throw new OdeFault(__msgs.msgBindingInputNotFound(_serviceName, _portName, op.getName()));
SOAPBody soapBody = getSOAPBody(bi);
if (soapBody != null)
extractSoapBodyParts(odeMessage, envelope.getBody(), soapBody, op.getInput().getMessage(), op.getName());
if (envelope.getHeader() != null)
extractSoapHeaderParts(odeMessage, envelope.getHeader(), getSOAPHeaders(bi), op.getInput().getMessage());
}
public void parseSoapResponse(org.apache.ode.bpel.iapi.Message odeMessage,
SOAPEnvelope envelope, Operation op) throws AxisFault {
BindingOperation bop = _binding.getBindingOperation(op.getName(), null, null);
if (bop == null)
throw new OdeFault(__msgs.msgBindingOperationNotFound(_serviceName, _portName, op.getName()));
BindingOutput bo = bop.getBindingOutput();
if (bo == null)
throw new OdeFault(__msgs.msgBindingInputNotFound(_serviceName, _portName, op.getName()));
SOAPBody soapBody = getSOAPBody(bo);
if (soapBody != null)
extractSoapBodyParts(odeMessage, envelope.getBody(), soapBody, op.getOutput().getMessage(), op.getName() + "Response");
if (envelope.getHeader() != null)
extractSoapHeaderParts(odeMessage, envelope.getHeader(), getSOAPHeaders(bo), op.getOutput().getMessage());
}
@SuppressWarnings("unchecked")
public void createSoapBody(org.apache.axiom.soap.SOAPBody sb, SOAPBody soapBody, Message msgDef,
Element message, String rpcWrapper) throws AxisFault {
if (msgDef == null)
return;
OMElement partHolder = _isRPC ? _soapFactory
.createOMElement(new QName(soapBody.getNamespaceURI(), rpcWrapper, "odens"), sb) : sb;
List<Part> parts = msgDef.getOrderedParts(soapBody.getParts());
for (Part part : parts) {
Element srcPartEl = DOMUtils.findChildByName(message, new QName(null, part.getName()));
if (srcPartEl == null)
throw new OdeFault(__msgs.msgOdeMessageMissingRequiredPart(part.getName()));
OMElement omPart = OMUtils.toOM(srcPartEl, _soapFactory);
if (_isRPC) partHolder.addChild(omPart);
else for (Iterator<OMNode> i = omPart.getChildren(); i.hasNext();) {
OMNode node = i.next();
i.remove();
partHolder.addChild(node);
}
}
}
// public Element createODEMessage(SOAPEnvelope soapEnv,Operation op) throws AxisFault {
// }
@SuppressWarnings("unchecked")
public void extractSoapBodyParts(org.apache.ode.bpel.iapi.Message message, org.apache.axiom.soap.SOAPBody soapBody,
SOAPBody bodyDef, Message msg,String rpcWrapper) throws AxisFault {
List<Part> bodyParts = msg.getOrderedParts(bodyDef.getParts());
if (_isRPC) {
QName rpcWrapQName = new QName(bodyDef.getNamespaceURI(), rpcWrapper);
OMElement partWrapper = soapBody.getFirstChildWithName(rpcWrapQName);
if (partWrapper == null)
throw new OdeFault(__msgs.msgSoapBodyDoesNotContainExpectedPartWrapper(_serviceName,_portName,rpcWrapQName));
// In RPC the body element is the operation name, wrapping parts. Order doesn't really matter as far as
// we're concerned. All we need to do is copy the soap:body children, since doc-lit rpc looks the same
// in ode and soap.
for (Part pdef : bodyParts) {
OMElement srcPart = partWrapper.getFirstChildWithName(new QName(null, pdef.getName()));
if (srcPart == null)
throw new OdeFault(__msgs.msgSOAPBodyDoesNotContainRequiredPart(pdef.getName()));
message.setPart(srcPart.getLocalName(), OMUtils.toDOM(srcPart));
}
} else {
// In doc-literal style, we expect the elements in the body to correspond (in order) to the
// parts defined in the binding. All the parts should be element-typed, otherwise it is a mess.
Iterator<OMElement> srcParts = soapBody.getChildElements();
for (Part partDef : bodyParts) {
if (!srcParts.hasNext())
throw new OdeFault(__msgs.msgSOAPBodyDoesNotContainRequiredPart(partDef.getName()));
OMElement srcPart = srcParts.next();
if (partDef.getElementName() == null)
throw new OdeFault(__msgs.msgBindingDefinesNonElementDocListParts());
if (!srcPart.getQName().equals(partDef.getElementName()))
throw new OdeFault(__msgs.msgUnexpectedElementInSOAPBody(srcPart.getQName(), partDef.getElementName()));
Document doc = DOMUtils.newDocument();
Element destPart = doc.createElementNS(null, partDef.getName());
destPart.appendChild(doc.importNode(OMUtils.toDOM(srcPart), true));
message.setPart(partDef.getName(), destPart);
}
}
}
public void extractSoapHeaderParts(org.apache.ode.bpel.iapi.Message message,
org.apache.axiom.soap.SOAPHeader soapHeader,
List<SOAPHeader> headerDefs, Message msg) throws AxisFault {
// Checking that the definitions we have are at least there
for (SOAPHeader headerDef : headerDefs)
handleSoapHeaderPartDef(message, soapHeader, headerDef, msg);
// Extracting whatever header elements we find in the message, binding and abstract parts
// aren't reliable enough given what people do out there.
Iterator headersIter = soapHeader.getChildElements();
while (headersIter.hasNext()) {
OMElement header = (OMElement) headersIter.next();
String partName = findHeaderPartName(headerDefs, header.getQName());
message.setHeaderPart(partName, OMUtils.toDOM(header));
}
}
private void handleSoapHeaderPartDef(org.apache.ode.bpel.iapi.Message odeMessage, org.apache.axiom.soap.SOAPHeader header, SOAPHeader headerdef,
Message msgType) throws AxisFault {
// Is this header part of the "payload" messsage?
boolean payloadMessageHeader = headerdef.getMessage() == null || headerdef.getMessage().equals(msgType.getQName());
boolean requiredHeader = payloadMessageHeader || (headerdef.getRequired() != null && headerdef.getRequired());
if (requiredHeader && header == null)
throw new OdeFault(__msgs.msgSoapHeaderMissingRequiredElement(headerdef.getElementType()));
if (header == null)
return;
Message hdrMsg = _def.getMessage(headerdef.getMessage());
if (hdrMsg == null)
return;
Part p = hdrMsg.getPart(headerdef.getPart());
if (p == null || p.getElementName() == null)
return;
OMElement headerEl = header.getFirstChildWithName(p.getElementName());
if (requiredHeader && headerEl == null)
throw new OdeFault(__msgs.msgSoapHeaderMissingRequiredElement(headerdef.getElementType()));
if (headerEl == null) return;
odeMessage.setHeaderPart(p.getName(), OMUtils.toDOM(headerEl));
}
private String findHeaderPartName(List<SOAPHeader> headerDefs, QName elmtName) {
for (SOAPHeader headerDef : headerDefs) {
Message hdrMsg = _def.getMessage(headerDef.getMessage());
for (Object o : hdrMsg.getParts().values()) {
Part p = (Part) o;
if (p.getElementName().equals(elmtName) && p.getName().equals(headerDef.getPart()))
return p.getName();
}
}
return elmtName.getLocalPart();
}
public static SOAPBody getSOAPBody(ElementExtensible ee) {
return getFirstExtensibilityElement(ee, SOAPBody.class);
}
@SuppressWarnings("unchecked")
public static List<SOAPHeader> getSOAPHeaders(ElementExtensible eee) {
if (eee == null)
return null;
return CollectionsX.filter(new ArrayList<SOAPHeader>(), (Collection<Object>) eee.getExtensibilityElements(),
SOAPHeader.class);
}
public static <T> T getFirstExtensibilityElement(ElementExtensible parent, Class<T> cls) {
if (parent == null)
return null;
Collection<T> ee = CollectionsX.filter(parent.getExtensibilityElements(), cls);
return ee.isEmpty() ? null : ee.iterator().next();
}
/**
* Attempts to extract the WS-Addressing "Action" attribute value from the operation definition.
* When WS-Addressing is being used by a service provider, the "Action" is specified in the
* portType->operation instead of the SOAP binding->operation.
*
* @param operation The name of the operation to extract the SOAP Action from
* @return the SOAPAction value if one is specified, otherwise empty string
*/
public String getWSAInputAction(String operation) {
BindingOperation bop = _binding.getBindingOperation(operation, null, null);
if (bop == null) return "";
Input input = bop.getOperation().getInput();
if (input != null) {
Object actionQName = input.getExtensionAttribute(new QName(Namespaces.WS_ADDRESSING_NS, "Action"));
if (actionQName != null && actionQName instanceof QName)
return ((QName)actionQName).getLocalPart();
}
return "";
}
/**
* Attempts to extract the SOAP Action is defined in the WSDL document.
*
* @param operation The name of the operation to extract the SOAP Action from
* @return the SOAPAction value if one is specified, otherwise empty string
*/
public String getSoapAction(String operation) {
BindingOperation bop = _binding.getBindingOperation(operation, null, null);
if (bop == null)
return "";
for (SOAPOperation soapOp : CollectionsX.filter(bop.getExtensibilityElements(), SOAPOperation.class))
return soapOp.getSoapActionURI();
return "";
}
public Fault parseSoapFault(Element odeMsgEl, SOAPEnvelope envelope, Operation operation) throws AxisFault {
SOAPFault flt = envelope.getBody().getFault();
SOAPFaultDetail detail = flt.getDetail();
Fault fdef = inferFault(operation, flt);
if (fdef == null)
return null;
Part pdef = (Part)fdef.getMessage().getParts().values().iterator().next();
Element partel = odeMsgEl.getOwnerDocument().createElementNS(null,pdef.getName());
odeMsgEl.appendChild(partel);
if (detail.getFirstChildWithName(pdef.getElementName()) != null) {
partel.appendChild(odeMsgEl.getOwnerDocument().importNode(
OMUtils.toDOM(detail.getFirstChildWithName(pdef.getElementName())), true));
} else {
partel.appendChild(odeMsgEl.getOwnerDocument().importNode(OMUtils.toDOM(detail),true));
}
return fdef;
}
@SuppressWarnings("unchecked")
private Fault inferFault(Operation operation, SOAPFault flt) {
if (flt.getDetail() == null || flt.getDetail().getFirstElement() == null)
return null;
// The detail is a dummy <detail> node containing the interesting fault element
QName elName = flt.getDetail().getFirstElement().getQName();
return WsdlUtils.inferFault(operation, elName);
}
}