/* * 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.tuscany.sca.binding.ws.jaxws; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; import javax.wsdl.Binding; import javax.wsdl.BindingOperation; import javax.wsdl.Definition; import javax.wsdl.Input; import javax.wsdl.OperationType; import javax.wsdl.PortType; import javax.wsdl.extensions.AttributeExtensible; import javax.wsdl.extensions.soap.SOAPAddress; import javax.wsdl.extensions.soap.SOAPOperation; import javax.wsdl.extensions.soap12.SOAP12Address; import javax.wsdl.extensions.soap12.SOAP12Operation; import javax.xml.namespace.QName; import javax.xml.soap.Detail; import javax.xml.soap.DetailEntry; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.ws.Dispatch; import javax.xml.ws.Service; import javax.xml.ws.WebServiceException; import javax.xml.ws.WebServiceFeature; import javax.xml.ws.soap.SOAPBinding; import javax.xml.ws.soap.SOAPFaultException; import org.apache.tuscany.sca.assembly.ComponentReference; import org.apache.tuscany.sca.assembly.Endpoint; import org.apache.tuscany.sca.binding.ws.WebServiceBinding; import org.apache.tuscany.sca.core.invocation.Constants; import org.apache.tuscany.sca.interfacedef.Operation; import org.apache.tuscany.sca.interfacedef.util.FaultException; import org.apache.tuscany.sca.interfacedef.wsdl.WSDLInterface; import org.apache.tuscany.sca.invocation.DataExchangeSemantics; import org.apache.tuscany.sca.invocation.Invoker; import org.apache.tuscany.sca.invocation.Message; import org.apache.tuscany.sca.runtime.RuntimeEndpointReference; import org.oasisopen.sca.ServiceRuntimeException; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Uses JAXWS Dispatch to invoke a remote web service * * @version $Rev$ $Date$ */ public class JAXWSBindingInvoker implements Invoker, DataExchangeSemantics { private final static String SCA11_TUSCANY_NS = "http://tuscany.apache.org/xmlns/sca/1.1"; public static final String WSA_FINAL_NAMESPACE = "http://www.w3.org/2005/08/addressing"; public static final QName QNAME_WSA_ADDRESS = new QName(WSA_FINAL_NAMESPACE, "Address", "wsa"); public static final QName QNAME_WSA_FROM = new QName(WSA_FINAL_NAMESPACE, "From", "wsa"); public static final QName QNAME_WSA_MESSAGEID = new QName(WSA_FINAL_NAMESPACE, "MessageID", "wsa"); public static final QName QNAME_WSA_TO = new QName(WSA_FINAL_NAMESPACE, "To", "wsa"); public static final QName QNAME_WSA_ACTION = new QName(WSA_FINAL_NAMESPACE, "Action", "wsa"); public static final QName QNAME_WSA_RELATESTO = new QName(WSA_FINAL_NAMESPACE, "RelatesTo", "wsa"); private static final QName submissionWSAWNS = new QName("http://schemas.xmlsoap.org/ws/2004/08/addressing", QNAME_WSA_ACTION.getLocalPart()); private static final QName finalWSANS = new QName("http://www.w3.org/2005/08/addressing", QNAME_WSA_ACTION.getLocalPart()); private static final QName finalWSAWNS = new QName("http://www.w3.org/2006/05/addressing/wsdl", QNAME_WSA_ACTION.getLocalPart()); private static final QName finalWSAMNS = new QName("http://www.w3.org/2007/05/addressing/metadata", QNAME_WSA_ACTION.getLocalPart()); public static final String TUSCANY_PREFIX = "tuscany"; public static final QName CALLBACK_ID_REFPARM_QN = new QName(SCA11_TUSCANY_NS, "CallbackID", TUSCANY_PREFIX); public static final QName CONVERSATION_ID_REFPARM_QN = new QName(SCA11_TUSCANY_NS, "ConversationID", TUSCANY_PREFIX); private boolean dynamicDispatchForCallback = false; protected Dispatch staticDispatch; private MessageFactory messageFactory; private Operation operation; protected WebServiceBinding wsBinding; private RuntimeEndpointReference endpointReference; public JAXWSBindingInvoker(Operation operation, WebServiceFeature[] features, MessageFactory messageFactory, WebServiceBinding wsBinding, RuntimeEndpointReference endpointReference) { this.messageFactory = messageFactory; this.operation = operation; this.wsBinding = wsBinding; this.endpointReference = endpointReference; if (endpointReference.getReference().isForCallback()) { this.dynamicDispatchForCallback = true; } else { this.staticDispatch = createStaticDispatch(); } } protected Dispatch createDynamicDispatch() { QName serviceName = wsBinding.getService().getQName(); QName portName = new QName(serviceName.getNamespaceURI(), wsBinding.getPort().getName()); Service service = Service.create(serviceName); service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpointReference.getDeployedURI()); return service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE); } protected Dispatch createStaticDispatch() { URL wsdlLocation = null; try { if (wsBinding.getGeneratedWSDLDocument() != null && wsBinding.getGeneratedWSDLDocument().getDocumentBaseURI() != null) { wsdlLocation = new URL(wsBinding.getGeneratedWSDLDocument().getDocumentBaseURI()); } } catch (Exception e) { // ignore and try getting the location from the other places } try { if (wsBinding.getUserSpecifiedWSDLDefinition().getLocation() != null) { wsdlLocation = wsBinding.getUserSpecifiedWSDLDefinition().getLocation().toURL(); } } catch (MalformedURLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } if (wsdlLocation != null) { return createDispatchFromWSDL(wsdlLocation); } else { return createDispatchFromURI(endpointReference.getDeployedURI()); } } protected Dispatch createDynamicDispatch(String uri) { return createDispatchFromURI(uri); } private Dispatch createDispatchFromWSDL(URL wsdlLocation) { QName serviceName = wsBinding.getServiceName(); QName portName = new QName(serviceName.getNamespaceURI(), wsBinding.getPortName()); Service service = Service.create(wsdlLocation, serviceName); return service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE); } protected Dispatch createDispatchFromURI(String uri) { QName serviceName = wsBinding.getService().getQName(); QName portName = new QName(serviceName.getNamespaceURI(), wsBinding.getPort().getName()); Service service = Service.create(serviceName); service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, uri); return service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE); } public Message invoke(Message msg) { try { SOAPMessage resp = invokeTarget(msg); if (resp != null) { SOAPBody body = resp.getSOAPBody(); if (body != null) { SOAPFault fault = body.getFault(); if (fault != null) { // setFault(msg, fault); } else { // WS-I uses single-element payload Element payload =(Element)body.getChildElements().next(); if (wsBinding.isRpcLiteral()) { Element unwrappedPayload = null; NodeList children = payload.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node nextChild = children.item(i); if (nextChild instanceof Element) { unwrappedPayload = (Element)nextChild; break; } } msg.setBody(unwrappedPayload); } else { msg.setBody(payload); } } } } } catch (SOAPFaultException e) { setFault(msg, e); } catch (WebServiceException e) { msg.setFaultBody(e); } catch (SOAPException e) { msg.setFaultBody(e); } catch (Throwable e) { msg.setFaultBody(e); } return msg; } private void setFault(Message msg, SOAPFaultException e) { SOAPFault fault = e.getFault(); Detail detail = fault.getDetail(); if (detail != null) { for (Iterator i = detail.getDetailEntries(); i.hasNext();) { DetailEntry entry = (DetailEntry)i.next(); FaultException fe = new FaultException(e.getMessage(), entry, e); fe.setFaultName(entry.getElementQName()); msg.setFaultBody(fe); } } else { msg.setFaultBody(e); } } protected String getSOAPAction(String operationName) { Binding binding = wsBinding.getBinding(); if (binding != null) { for (Object o : binding.getBindingOperations()) { BindingOperation bop = (BindingOperation)o; if (bop.getName().equalsIgnoreCase(operationName)) { for (Object o2 : bop.getExtensibilityElements()) { if (o2 instanceof SOAPOperation) { return ((SOAPOperation)o2).getSoapActionURI(); } else if (o2 instanceof SOAP12Operation) { return ((SOAP12Operation)o2).getSoapActionURI(); } } } } } return null; } protected SOAPMessage invokeTarget(Message msg) throws SOAPException { SOAPMessage soapMessage = messageFactory.createMessage(); SOAPPart soapPart = soapMessage.getSOAPPart(); javax.xml.soap.SOAPEnvelope envelope = soapPart.getEnvelope(); String action = getSOAPAction(operation.getName()); setHeaders(envelope.getHeader(), msg, action); javax.xml.soap.SOAPBody body = envelope.getBody(); Object[] args = (Object[])msg.getBody(); if (wsBinding.isRpcLiteral()) { String wrapperNamespace = null; // the rpc style creates a wrapper with a namespace where the namespace is // defined on the wsdl binding operation. If no binding is provided by the // user then default to the namespace of the WSDL itself. if (wsBinding.getBinding() != null){ Iterator iter = wsBinding.getBinding().getBindingOperations().iterator(); loopend: while(iter.hasNext()){ BindingOperation bOp = (BindingOperation)iter.next(); if (bOp.getName().equals(msg.getOperation().getName())){ for (Object ext : bOp.getBindingInput().getExtensibilityElements()){ if (ext instanceof javax.wsdl.extensions.soap.SOAPBody){ wrapperNamespace = ((javax.wsdl.extensions.soap.SOAPBody)ext).getNamespaceURI(); break loopend; } } } } } if (wrapperNamespace == null){ wrapperNamespace = wsBinding.getUserSpecifiedWSDLDefinition().getNamespace(); } Element rpcOperationWrapper = body.getOwnerDocument().createElementNS(wrapperNamespace, msg.getOperation().getName()); for (Object arg : args) { Node next = (Node)arg; Node nextImported = body.getOwnerDocument().importNode(next, true); rpcOperationWrapper.appendChild(nextImported); } body.appendChild(rpcOperationWrapper); } else if (wsBinding.isRpcEncoded()) { throw new ServiceRuntimeException("rpc/encoded WSDL style not supported for endpoint reference " + endpointReference); } else if (wsBinding.isDocEncoded()){ throw new ServiceRuntimeException("doc/encoded WSDL style not supported for endpoint reference " + endpointReference); } else { // In the unit test the owner doc is null // so explicitly adopt the node instead // body.addDocument(((Node)args[0]).getOwnerDocument()); Node msgNode = body.getOwnerDocument().importNode((Node)args[0], true); body.appendChild(msgNode); } soapMessage.saveChanges(); Dispatch invocationDispatch = null; //TODO - captured static case as well??? if (dynamicDispatchForCallback) { Endpoint ep = msg.getTo(); if (ep != null && ep.getBinding() != null) { String address = ep.getDeployedURI(); invocationDispatch = createDynamicDispatch(address); } else { throw new ServiceRuntimeException("[BWS20025] Unable to determine destination endpoint for endpoint reference " + endpointReference); } } else { invocationDispatch = staticDispatch; } if (operation.isNonBlocking()) { invocationDispatch.invokeOneWay(soapMessage); return null; } if (action != null) { invocationDispatch.getRequestContext().put(Dispatch.SOAPACTION_USE_PROPERTY, true); invocationDispatch.getRequestContext().put(Dispatch.SOAPACTION_URI_PROPERTY, action); } SOAPMessage response = invocationDispatch.invoke(soapMessage); return response; } protected void setHeaders(SOAPHeader sh, Message msg, String action) throws SOAPException { Endpoint callbackEndpoint = msg.getFrom().getCallbackEndpoint(); // add WS-Addressing header for the invocation of a bidirectional // service // FIXME: is there any way to use the Axis2 addressing support for this? // // IIUC, this 'if (callbackEndpoint != null)' will be true if: // 1) This is a bidirectional interface // AND // 2) We are invoking in the forward direction of the bidirectional interface. // if (callbackEndpoint != null) { // // Load the actual callback endpoint URI into an Axis EPR ready // to form the content of the wsa:From header // EndpointReference fromEPR = new // EndpointReference(callbackEndpoint.getBinding().getURI()); // // addWSAFromHeader(sh, fromEPR); SOAPHeaderElement fromH = sh.addHeaderElement(QNAME_WSA_FROM); SOAPElement fromAddress = fromH.addChildElement(QNAME_WSA_ADDRESS); fromAddress.setTextContent(callbackEndpoint.getDeployedURI()); addWSAActionHeader(sh, action); // We need a wsa:MessageId for request-response operation per WS-Addressing core specification, // (and Axis2 will choke if addressing module is enabled.) if (!operation.isNonBlocking()) { String messageId = UUID.randomUUID().toString(); SOAPHeaderElement msgIdHeader = sh.addHeaderElement(QNAME_WSA_MESSAGEID); msgIdHeader.setTextContent(messageId); } } // end if String toAddress = getToAddress(msg); // requestMC.setTo( new EndpointReference(toAddress) ); // IIUC, this 'if (callbackEndpoint != null)' will be true if: // 1) This is a bidirectional interface // AND // 2) We are invoking in the callback direction of the bidirectional interface. // if (isInvocationForCallback(msg)) { addWSAToHeader(sh, toAddress, msg); addWSARefParms(sh, msg); addWSAActionHeader(sh, action); addWSARelatesTo(sh, msg); } // end if } private String getToAddress(Message msg) throws ServiceRuntimeException { String address = null; // if target endpoint was not specified when this invoker was created, // use dynamically specified target endpoint passed in with the message String to = getPortLocation(); if (to == null) { Endpoint ep = msg.getTo(); if (ep != null && ep.getBinding() != null) { address = ep.getDeployedURI(); } else { throw new ServiceRuntimeException( "[BWS20025] Unable to determine destination endpoint for endpoint reference " + endpointReference); } } else { address = to; } return address; } // end method getToAddress protected String getPortLocation() { String ep = null; if (wsBinding.getPort() != null) { List wsdlPortExtensions = wsBinding.getPort().getExtensibilityElements(); for (final Object extension : wsdlPortExtensions) { if (extension instanceof SOAPAddress) { ep = ((SOAPAddress)extension).getLocationURI(); break; } if (extension instanceof SOAP12Address) { SOAP12Address address = (SOAP12Address)extension; ep = address.getLocationURI(); break; } } } if (ep == null || ep.equals("")) { ep = endpointReference.getDeployedURI(); } return ep; } // private void addWSAFromHeader( SOAPHeader sh, EndpointReference fromEPR ) // throws AxisFault { // OMElement epr = EndpointReferenceHelper.toOM(sh.getOMFactory(), // fromEPR, // QNAME_WSA_FROM, // AddressingConstants.Final.WSA_NAMESPACE); // sh.addChild(epr); // // } // end method addWSAFromHeader private static String WS_REF_PARMS = "WS_REFERENCE_PARAMETERS"; private void addWSAToHeader(SOAPHeader sh, String address, Message msg) throws SOAPException { // Create wsa:To header which is required by ws-addressing spec // OMElement wsaToOM = sh.getOMFactory().createOMElement(QNAME_WSA_TO); // wsaToOM.setText( address ); // sh.addChild(wsaToOM); SOAPHeaderElement toH = sh.addHeaderElement(QNAME_WSA_TO); toH.setTextContent(address); } // end method addWSAToHeader protected void addWSARefParms(SOAPHeader sh, Message msg) throws SOAPException { // Not implemented and so will not pass compliance test BWS_5006. } // end method addWSARefParms private void addWSAActionHeader(SOAPHeader sh, String action) throws SOAPException { // Create wsa:Action header which is required by ws-addressing spec if (action == null) { PortType portType = ((WSDLInterface)wsBinding.getBindingInterfaceContract().getInterface()).getPortType(); javax.wsdl.Operation op = portType.getOperation(operation.getName(), null, null); action = getActionFromInputElement(wsBinding.getGeneratedWSDLDocument(), portType, op, op.getInput()); } // OMElement actionOM = // sh.getOMFactory().createOMElement(QNAME_WSA_ACTION); // actionOM.setText(action == null ? "" : action); // sh.addChild(actionOM); SOAPHeaderElement actionH = sh.addHeaderElement(QNAME_WSA_ACTION); actionH.setTextContent(action == null ? "" : action); } // end method addWSAActionHeader protected static String SCA_CALLBACK_REL = "http://docs.oasis-open.org/opencsa/sca-bindings/ws/callback"; /** * Adds a wsa:RelatesTo SOAP header if the incoming invocation had a * wsa:MessageID SOAP header present - note that OASIS SCA requires that the * RelationshipType attribute is set to a particular SCA value * * @param sh - the SOAP headers * @param msg - the message * @throws SOAPException */ private void addWSARelatesTo(SOAPHeader sh, Message msg) throws SOAPException { // // Note that the 'core' (loosely speaking) part of the invocation chain // will have already copied the forward message msgId to the RELATES_TO header. // String idValue = (String)msg.getHeaders().get(Constants.RELATES_TO); if (idValue != null) { SOAPHeaderElement relatesToH = sh.addHeaderElement(QNAME_WSA_RELATESTO); relatesToH.addAttribute(new QName(null, "RelationshipType"), SCA_CALLBACK_REL); relatesToH.setTextContent(idValue); // OMElement relatesToOM = sh.getOMFactory().createOMElement( // QNAME_WSA_RELATESTO ); // OMAttribute relType = // sh.getOMFactory().createOMAttribute("RelationshipType", null, // SCA_CALLBACK_REL); // relatesToOM.addAttribute( relType ); // relatesToOM.setText( idValue ); // sh.addChild( relatesToOM ); } } // end method addWSARelatesTo /** * Indicates if the invocation is for the callback of a bidirectional * service * * @param msg the Message * @return true if the invocation is for the callback of a bidirectional * service, false otherwise */ private boolean isInvocationForCallback(Message msg) { org.apache.tuscany.sca.assembly.EndpointReference fromEPR = msg.getFrom(); if (fromEPR != null) { ComponentReference ref = fromEPR.getReference(); if (ref != null) return ref.isForCallback(); } // end if return false; } // end method isInvocationForCallback /** * getActionFromInputElement * * @param def the wsdl:definitions which contains the wsdl:portType * @param wsdl4jPortType the wsdl:portType which contains the wsdl:operation * @param op the wsdl:operation which contains the input element * @param input the input element to be examined to generate the wsa:Action * @return either the wsaw:Action from the input element or an action * generated using the DefaultActionPattern */ public static String getActionFromInputElement(Definition def, PortType wsdl4jPortType, javax.wsdl.Operation op, Input input) { String result = getWSAWActionExtensionAttribute(input); if (result == null) { result = generateActionFromInputElement(def, wsdl4jPortType, op, input); } return result; } private static String getWSAWActionExtensionAttribute(AttributeExtensible ae) { // Search first for a wsaw:Action using the submission namespace Object attribute = ae.getExtensionAttribute(submissionWSAWNS); // Then if that did not exist one using the w3c WSAM namespace if (attribute == null) { attribute = ae.getExtensionAttribute(finalWSAMNS); } // Then if that did not exist one using the w3c WSAW namespace // (for backwards compat reasons) if (attribute == null) { attribute = ae.getExtensionAttribute(finalWSAWNS); } // Then finally if that did not exist, try the 2005/08 NS // (Included here because it's needed for Apache Muse) if (attribute == null) { attribute = ae.getExtensionAttribute(finalWSANS); } // wsdl4j may return a String, QName or a List of either // If it is a list, extract the first element if (attribute instanceof List) { List l = (List)attribute; if (l.size() > 0) { attribute = l.get(0); } else { attribute = null; } } // attribute must now be a QName or String or null // If it is a QName, take the LocalPart as a String if (attribute instanceof QName) { QName qn = (QName)attribute; attribute = qn.getLocalPart(); } if ((attribute instanceof String)) { String result = (String)attribute; return result; } else { return null; } } /** * Generate the Action for an Input using the Default Action Pattern *

* Pattern is defined as [target namespace][delimiter][port type * name][delimiter][input name] * * @param def is required to obtain the targetNamespace * @param wsdl4jPortType is required to obtain the portType name * @param op is required to generate the input name if not explicitly * specified * @param input is required for its name if specified * @return a wsa:Action value based on the Default Action Pattern and the * provided objects */ public static String generateActionFromInputElement(Definition def, PortType wsdl4jPortType, javax.wsdl.Operation op, Input input) { // Get the targetNamespace of the wsdl:definitions String targetNamespace = def.getTargetNamespace(); // Determine the delimiter. Per the spec: 'is ":" when the [target // namespace] is a URN, otherwise "/". // Note that for IRI schemes other than URNs which aren't path-based // (i.e. those that outlaw the "/" // character), the default action value may not conform to the rules of // the IRI scheme. Authors // are advised to specify explicit values in the WSDL in this case.' String delimiter = SLASH; if (targetNamespace.toLowerCase().startsWith(URN)) { delimiter = COLON; } // Get the portType name (as a string to be included in the action) String portTypeName = wsdl4jPortType.getQName().getLocalPart(); // Get the name of the input element (and generate one if none // explicitly specified) String inputName = getNameFromInputElement(op, input); // Append the bits together StringBuffer sb = new StringBuffer(); sb.append(targetNamespace); // Deal with the problem that the targetNamespace may or may not have a // trailing delimiter if (!targetNamespace.endsWith(delimiter)) { sb.append(delimiter); } sb.append(portTypeName); sb.append(delimiter); sb.append(inputName); // Resolve the action from the StringBuffer String result = sb.toString(); return result; } /** * Get the name of the specified Input element using the rules defined in * WSDL 1.1 Section 2.4.5 http://www.w3.org/TR/wsdl#_names */ private static String getNameFromInputElement(javax.wsdl.Operation op, Input input) { // Get the name from the input element if specified. String result = input.getName(); // If not we'll have to generate it. if (result == null) { // If Request-Response or Solicit-Response do something special per // WSDL 1.1 Section 2.4.5 OperationType operationType = op.getStyle(); if (null != operationType) { if (operationType.equals(OperationType.REQUEST_RESPONSE)) { result = op.getName() + REQUEST; } else if (operationType.equals(OperationType.SOLICIT_RESPONSE)) { result = op.getName() + RESPONSE; } } // If the OperationType was not available for some reason, assume // on-way or notification if (result == null) { result = op.getName(); } } return result; } private static final String URN = "urn"; private static final String SLASH = "/"; private static final String COLON = ":"; private static final String REQUEST = "Request"; private static final String RESPONSE = "Response"; public boolean allowsPassByReference() { return true; } }