Continuing problem with XTS WS-T tests in AS trunk/CXF 2.2.9
adinn Jun 18, 2010 7:12 AMThe XTS tests are still seeing some failures on the latest AS trunk with cxf stack 3.3.1.SP1. I think they all appear to stem from a single issue which relates to dispatch of soap faults. It's not actually CXF's fault handling which is the problem. The XTS code uses its own JaxWS based service to dispatch faults -- I know that sounds weird but I'll explain that in a minute. Anyway, the problem is not actually to do with the dispatch, routing or receipt of the soap faults, it's to do with creating the relevant JaxWS service. This is a WSDL first service defined as follows:
<?xml version="1.0" encoding="utf-8"?>
<!--
a specification for a service which can be used to dispatch a SOAP 1.1 SoapFault
to an arbitrary client
-->
<definitions
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://jbossts.jboss.org/xts/soapfault"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsaw="http://www.w3.org/2006/02/addressing/wsdl"
targetNamespace="http://jbossts.jboss.org/xts/soapfault"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<s:schema>
<s:import namespace="http://schemas.xmlsoap.org/soap/envelope/"
schemaLocation="http://schemas.xmlsoap.org/soap/envelope"/>
</s:schema>
</types>
<message name="SoapFault">
<part name="fault" element="soapenv:Fault" />
</message>
<portType name="SoapFaultPortType">
<operation name="SoapFault">
<input name="SoapFault" message="tns:SoapFault" />
</operation>
</portType>
<binding name="SoapFault_SOAPBinding" type="tns:SoapFaultPortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="SoapFault">
<input message="tns:Soapfault">
<soap:body use="literal"/>
</input>
</operation>
</binding>
<service name="SoapFaultService">
<port binding="tns:SoapFault_SOAPBinding" name="SoapFaultPortType">
<wsaw:UsingAddressing required="true"/>
<soap:address location="http://localhost:9000/interopat/SoapFaultService"/>
</port>
</service>
</definitions>
I generated code from this WSDL using wsconsume (well, actually, I think I generated it using Glassfish's wsimport but essentially it is the same thing) as follows:
@WebServiceClient(name = "SoapFaultService", targetNamespace = "http://jbossts.jboss.org/xts/soapfault", wsdlLocation = "wsdl/soapfault.wsdl")
public class SoapFaultService extends Service
{
private final static URL SOAPFAULTSERVICE_WSDL_LOCATION;
private final static Logger logger = Logger.getLogger(org.jboss.jbossts.xts.soapfault.SoapFaultService.class.getName());
static {
URL url = null;
try {
URL baseUrl;
baseUrl = org.jboss.jbossts.xts.soapfault.SoapFaultService.class.getResource(".");
url = new URL(baseUrl, "wsdl/soapfault.wsdl");
} catch (MalformedURLException e) {
logger.warning("Failed to create URL for the wsdl Location: 'wsdl/soapfault.wsdl', retrying as a local file");
logger.warning(e.getMessage());
}
SOAPFAULTSERVICE_WSDL_LOCATION = url;
}
public SoapFaultService(URL wsdlLocation, QName serviceName) {
super(wsdlLocation, serviceName);
}
public SoapFaultService() {
super(SOAPFAULTSERVICE_WSDL_LOCATION, new QName("http://jbossts.jboss.org/xts/soapfault", "SoapFaultService"));
}
/**
*
* @return
* returns SoapFaultPortType
*/
@WebEndpoint(name = "SoapFaultPortType")
public SoapFaultPortType getSoapFaultPortType() {
return super.getPort(new QName("http://jbossts.jboss.org/xts/soapfault", "SoapFaultPortType"), SoapFaultPortType.class);
}
/**
*
* @param features
* A list of {@link javax.xml.ws.WebServiceFeature} to configure on the proxy. Supported features not in the <code>features</code> parameter will have their default values.
* @return
* returns SoapFaultPortType
*/
@WebEndpoint(name = "SoapFaultPortType")
public SoapFaultPortType getSoapFaultPortType(WebServiceFeature... features) {
return super.getPort(new QName("http://jbossts.jboss.org/xts/soapfault", "SoapFaultPortType"), SoapFaultPortType.class, features);
}
}
@WebService(name = "SoapFaultPortType", targetNamespace = "http://jbossts.jboss.org/xts/soapfault")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@XmlSeeAlso({
ObjectFactory.class
})
public interface SoapFaultPortType {
/**
*
* @param fault
*/
@WebMethod(operationName = "SoapFault")
@Oneway
public void soapFault(
@WebParam(name = "Fault", targetNamespace = "http://schemas.xmlsoap.org/soap/envelope/", partName = "fault")
Fault fault);
}
The problem is that when my tests try to create a SoapFaultPortType I get a parse error during processing of the WSDL. Unfortunately, the error is not reported as being in my WDSL. It ahppens wen processing the imported schema
<s:schema>
<s:import namespace="http://schemas.xmlsoap.org/soap/envelope/"
schemaLocation="http://schemas.xmlsoap.org/soap/envelope"/>
</s:schema>
I stepped through the code and found that it successfully loads the schema located at http://schemas.xmlsoap.org/soap/envelope but then runs into problems processing the contents. Here is the error message:
[Fatal Error] strict.dtd:81:5: The declaration for the entity "ContentType" must end with '>'.
[Fatal Error] envelope:2:2: The markup in the document following the root element must be well-formed.
javax.xml.ws.WebServiceException: org.apache.cxf.service.factory.ServiceConstructionException: Failed to create service.
at org.apache.cxf.jaxws.ServiceImpl.<init>(ServiceImpl.java:152)
at org.apache.cxf.jaxws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:63)
at javax.xml.ws.Service.<init>(Service.java:57)
at org.jboss.jbossts.xts.soapfault.SoapFaultService.<init>(SoapFaultService.java:46)
at com.arjuna.webservices11.wsaddr.client.SoapFaultClient.getSoapFaultService(SoapFaultClient.java:98)
at com.arjuna.webservices11.wsaddr.client.SoapFaultClient.getSoapFaultPort(SoapFaultClient.java:130)
at com.arjuna.webservices11.wsaddr.client.SoapFaultClient.sendSoapFault(SoapFaultClient.java:86)
at com.arjuna.webservices11.wsat.client.CompletionInitiatorClient.sendSoapFault(CompletionInitiatorClient.java:123)
at com.arjuna.wst11.tests.junit.CompletionCoordinatorTestCase.testSendError(CompletionCoordinatorTestCase.java:180)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:24)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:24)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
at com.arjuna.qa.junit.TestRunnerServlet$RunnerThread.run(TestRunnerServlet.java:523)
Caused by: org.apache.cxf.service.factory.ServiceConstructionException: Failed to create service.
at org.apache.cxf.wsdl11.WSDLServiceFactory.<init>(WSDLServiceFactory.java:93)
at org.apache.cxf.jaxws.ServiceImpl.initializePorts(ServiceImpl.java:207)
at org.apache.cxf.jaxws.ServiceImpl.<init>(ServiceImpl.java:150)
... 46 more
Caused by: javax.wsdl.WSDLException: WSDLException (at /definitions/types/s:schema): faultCode=PARSER_ERROR: Problem parsing 'http://schemas.xmlsoap.org/soap/envelope'.: org.xml.sax.SAXParseException: The markup in the document following the root element must be well-formed.
at com.ibm.wsdl.xml.WSDLReaderImpl.getDocument(Unknown Source)
at com.ibm.wsdl.xml.WSDLReaderImpl.parseSchema(Unknown Source)
at com.ibm.wsdl.xml.WSDLReaderImpl.parseSchema(Unknown Source)
at com.ibm.wsdl.xml.WSDLReaderImpl.parseTypes(Unknown Source)
at com.ibm.wsdl.xml.WSDLReaderImpl.parseDefinitions(Unknown Source)
at com.ibm.wsdl.xml.WSDLReaderImpl.readWSDL(Unknown Source)
at com.ibm.wsdl.xml.WSDLReaderImpl.readWSDL(Unknown Source)
at org.apache.cxf.wsdl11.WSDLManagerImpl.loadDefinition(WSDLManagerImpl.java:230)
at org.apache.cxf.wsdl11.WSDLManagerImpl.getDefinition(WSDLManagerImpl.java:179)
at org.apache.cxf.wsdl11.WSDLServiceFactory.<init>(WSDLServiceFactory.java:91)
... 48 more
Caused by: org.xml.sax.SAXParseException: The markup in the document following the root element must be well-formed.
at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
... 58 more
As you can see the problem occurs under ServiceImple.<init> below SoapFaultService.<init> when trying to create the SoapFaultService class generated by wsconsume. This is somewhat bizarre given where the schema comes from.
Ok, so you might want an explanation for what this servcie is and why it needs to import the soap envelope schema. It's a bit convoluted but here goes. XTS implements the WS-C and WS-T services defined by the WSTX standard. WS-C includes two generic services for creating and enlisting with transactions from various transaction-specific protocols. WS-T includes two families of transaction protocol specific services which allow transaction clients and transactional web services to progress towards transaction completion. The service APIs are specified by WSDL and our implementation uses JaxWS to implement that WSDL.
The WS-T services (WS-AT atomic transaction and WS-BA business activity), are mandated only to employ a OneWay MEP. Tthe SOAP used to deliver these messages is required to be configured with various WSA headers, including MessageId, ReplyTo and FaultTo. The SOAP headers also need to include service specific transaction identifiers as header elements. This is achieved by by installing them as ReferenceParameters in the endpoints used by the transaction protocol services and transactional web services communicate ie. they are attached to
- the transaction protocol specific Coordinator service endpoints handed out to transactional web services by the WS-C Registration service when they enlist in a transaction
- the endpoint in the ReplyTo/FaultTo header included by web services when they negotiate with the WS-C Registration service or with their Coordinator service
Yes, as you can see, the use of one way messaging means that the container for the transactional web service must also run a WS-AT or WS-BA Initiator service. The Coordinator talks back to this Initiator service in order to be able to constuct a long-running dialogue with the transactional web service composed of a series of 1-way messages such as e.g. enlist, prepare, prepared, commit, commited. In the case of WS-BA this dialogue can be initiated asynchronously from either end -- that's why OneWay MEP was chosen over RPC.
The service specification mandates that in cases where processing of a message runs into certain error conditions a SoapFault must be dispatched to the FaultTo address configured in the WSA headers. Obviously, if a fault occurs in delivering the message the JaxWS layer will dispatch a fault to the sender. But that's not that the spec is talking about (or at least not all it is talking about). The spec is concerned with faults which occur after delivery when the delivered message is being processed. By this stage JaxWS has done its stuff and told the sender 'message delivered'. If the service endpoint bean were to throw a fault at this point nothing would get dispatched back to the sender. So, the threads which are handling a request to the Coordinator or Initiator services need to be able to dispatch a fault to the FaultTo address asynchronously -- from a standing start. This fault needs to
- be sent to the FaultTo address
- provide a WSA RelatesTo header which includes the originating MessageId
- include the ReferenceParameter header obtained with the FaultTo address.
Now the WS-AT/BA Coordinator and Initiator service endpoint beans all implement a WebMethod which accepts a SoapFault with a specific SOAPAction or WSA Action. This web method is declared by interface SoapFaultPortType generated from the SoapFaultService WSDL and all the Coordinator/Initiator services implement the interface i.e. they are capable of acting as an endpoint for the SoapFaultPortType of the SoapFaultService as well as an endpont for the transaction-specific protocol. So, in order to dispatch an asynchronous SoapFault to the sender the handler thread merely needs to create a fault and an instance of SoapFaultService, clone a proxy port from it and call method soapFault(). So this is what is happening when my current implementation blows up trying to read the soap envelope WSDL.