Spring Web-Service unmarshalling not working - web-services

I have configured my WebService like this:
applicationContext:
<sws:annotation-driven />
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping" >
<property name="interceptors">
<list>
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
</list>
</property>
Note: the Interceptor is loaded on startup, but doesn´t write anything, if a request is coming in.
I have a class PersonServiceImpl with the method addPersonRequest(). Everything works , if i am using org.dom4j.Element as method parameter;
#Endpoint
public class PersonServiceImpl {
#PayloadRoot(namespace = "http://www.example.org/person/schema", localPart = "AddPersonRequest")
#ResponsePayload
public AddPersonRequest addPersonRequest(#RequestPayload Element element) {
System.out.println(element.asXML());
Person response = new Person();
response.setId(2);
response.setFirstName("Mad");
response.setLastName("Mike");
return response;
}
}
But if i change my method parameters like shown below (so auto-marshalling of spring-ws should be used) the request.getFirstName() prints null. (JAXB2 is on classpath).
The Person-class is annotated with #XMLType and #XMLRootElement.
Note: The marshalling works fine.
#Endpoint
public class PersonServiceImpl {
#PayloadRoot(namespace = "http://www.example.org/person/schema", localPart = "AddPersonRequest")
#ResponsePayload
public AddPersonRequest addPersonRequest(#RequestPayload Person request, SoapHeader header) {
System.out.println(header.getName());
System.out.println(request.getFirstName());
Person response = new Person();
response.setId(2);
response.setFirstName("Mad");
response.setLastName("Mike");
return response;
}
}
Person.java:
#XmlType
#XmlRootElement(namespace="http://www.example.org/person/schema", name="Person")
public class Person implements Serializable {
private int id;
private String firstName;
private String lastName;
#XmlElement
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#XmlElement
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Test-Request sent via soapUI (generated from wsdl):
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="http://www.example.org/person/schema">
<soapenv:Header/>
<soapenv:Body>
<sch:AddPersonRequest>
<sch:Person sch:Id="1">
<sch:FirstName>firstname</sch:FirstName>
<sch:LastName>lastname</sch:LastName>
</sch:Person>
</sch:AddPersonRequest>
</soapenv:Body>
</soapenv:Envelope>

I'm not sure if you still need an answer, reading your latest comment. I'm a bit confused with your request and response payload. They seem to be switched. Anyway, that's hard to say without the Person class.
I've dealt with similar issues before though and those were solved by adding JAXBElement<> around the actual class. Like this snippet:
#PayloadRoot(
localPart = "PutOrganisationUnitRequest",
namespace = DEFAULT_NAMESPACE
)
#ResponsePayload public JAXBElement<Response> putOrganisationUnits (
#RequestPayload JAXBElement<PutOrganisationUnitRequest> organisations,
MessageContext messageContext) {
One other thing you can check is the namespaces in your jaxb class and in your endpoint definition.

You mentioned marshalling is working I don't see any reason why unmarshalling won't work, Have you tested marshall and unmarshall in isloation?
Just to make sure the soap request is good, Can you add logging interceptor and print the actual request that is coming from the client accessing web service, Add this snippet to your context file
<sws:interceptors>
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.SoapEnvelopeLoggingInterceptor">
<property name="logRequest" value="true"></property>
<property name="logResponse" value="true"></property>
</bean>
</sws:interceptors>
You should see a log message like this that contains the entire soap request, I am adding a log message request from a saop UI to
DEBUG [http-8080-2]:endpoint.interceptor.SoapEnvelopeLoggingInterceptor.logMessage - Request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="http://hoost:port/context/patient/schemas">
<soapenv:Header/>
<soapenv:Body>
<sch:addRequest>
<sch:patient>
<sch:id>?</sch:id>
<sch:lastname>Joe</sch:lastname>
</sch:patient>
</sch:addRequest>
</soapenv:Body>
</soapenv:Envelope>
UPDATE of answer
It could be your soap request not sure though
<sch:FirstName>firstname</sch:FirstName> (this should be)
<sch:firstName>firstname</sch:firstName>
Another update
The exception is due to the way you defined the end point, In your soap request (sch:AddPersonRequest) you are sending a addPersonRequest not Person as payload so change the end point to reflect that, The #RequestPayload should be AddPersonRequest not Person
#PayloadRoot(namespace = "http://www.example.org/person/schema", localPart = "AddPersonRequest")
#ResponsePayload
public AddPersonRequest addPersonRequest(#RequestPayload AddPersonRequest request, SoapHeader header) {

This Spring webservice implementation is utter garbage. A brain dead idiot could do work better than those "pros".
How hard it was to just follow the JSON principles and restore object tree following offered root type.
NO-ONE needs these namespaces. It never happens that the same request have two elements with the same name but from different namespaces. But these namespaces is endless pain.
Just match XML elements to object fields. This only what is required. Assign namespaces from WSDL when generating responses if anyone cares them on retrieving end but completely ignore them when unmarshaling a request.
When I look at all of this I really want to spend a month and do a NORMAL SOAP webservice framework with XML serialization/deserialization that only cares about element names and auto instantiate request/response objects as long as XML tree is matching object tree.

Related

How do I set the WS-Addressing MessageId header when using CXF with Apache Camel?

I'm invoking a web service that requires WS-Addressing SOAP headers. I'm using Apache Camel with CXF to invoke the web service. When I configure the CXF endpoint with the web service's WSDL, it's smart enough to automatically add WS-Adressing SOAP headers, but I need to set a custom MessageId.
Here is the message that is currently being sent:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<ws:international xmlns:ws="http://www.w3.org/2005/09/ws-i18n">
<ws:locale xmlns:ws="http://www.w3.org/2005/09/ws-i18n">en_CA</ws:locale>
</ws:international>
<fram:user wsa:IsReferenceParameter="true" xmlns:fram="http://wsbo.webservice.ephs.pdc.ibm.com/Framework/" xmlns:wsa="http://www.w3.org/2005/08/addressing">BESTSystem</fram:user>
<Action soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">http://webservice.ephs.pdc.ibm.com/Client/QueryHumanSubjects</Action>
<MessageID soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:945cfd10-9fd2-48f9-80b4-ac1b9f3293c6</MessageID>
<To soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">https://panweb5.panorama.gov.bc.ca:8081/ClientWebServicesWeb/ClientProvider</To>
<ReplyTo soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
</ReplyTo>
</soap:Header>
<soap:Body>
<ns2:queryHumanSubjectsRequest xmlns:ns2="http://wsbo.webservice.ephs.pdc.ibm.com/Client/" xmlns:ns3="http://wsbo.webservice.ephs.pdc.ibm.com/FamilyHealth/">
<!-- stuff -->
</ns2:queryHumanSubjectsRequest>
</soap:Body>
</soap:Envelope>
As you can see, the MessageId value is "urn:uuid:945cfd10-9fd2-48f9-80b4-ac1b9f3293c6". I need to set a custom value.
I tried adding the MessageId header they way I add the other headers like "international" and "user", but some part of the framework overrides the value.
// Note this doesn't work! Something overrides the value. It works for other headers.
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
List<SoapHeader> headers = CastUtils.cast((List<?>) in.getHeader(Header.HEADER_LIST));
SOAPFactory sf = SOAPFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
QName MESSAGE_ID_HEADER = new QName("http://www.w3.org/2005/08/addressing", "MessageID", "wsa");
SOAPElement messageId = sf.createElement(MESSAGE_ID_HEADER);
messageId.setTextContent("customValue");
SoapHeader soapHeader = new SoapHeader(MESSAGE_ID_HEADER, messageId);
headers.add(soapHeader);
}
The CXF website has some documentation on how to set WS-Addressing headers, but I don't see how to apply it to Apache Camel. The Apache Camel CXF documentation doesn't specifically mention WS-Addressing either.
The documentation links you posted actually do have the information you need, although it's not immediately obvious how to apply it to Camel.
The CXF documentation says that:
The CXF org.apache.cxf.ws.addressing.impl.AddressingPropertiesImpl object can be used to control many aspects of WS-Addressing including the Reply-To:
AddressingProperties maps = new AddressingPropertiesImpl();
EndpointReferenceType ref = new EndpointReferenceType();
AttributedURIType add = new AttributedURIType();
add.setValue("http://localhost:9090/decoupled_endpoint");
ref.setAddress(add);
maps.setReplyTo(ref);
maps.setFaultTo(ref);
((BindingProvider)port).getRequestContext()
.put("javax.xml.ws.addressing.context", maps);
Note that it sets the addressing properties on the "RequestContext".
The Apache Camel documentation says that:
How to propagate a camel-cxf endpoint’s request and response context
CXF client API provides a way to invoke the operation with request and response context. If you are using a camel-cxf endpoint producer to invoke the outside web service, you can set the request context and get response context with the following code:
CxfExchange exchange = (CxfExchange)template.send(getJaxwsEndpointUri(), new Processor() {
public void process(final Exchange exchange) {
final List<String> params = new ArrayList<String>();
params.add(TEST_MESSAGE);
// Set the request context to the inMessage
Map<String, Object> requestContext = new HashMap<String, Object>();
requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, JAXWS_SERVER_ADDRESS);
exchange.getIn().setBody(params);
exchange.getIn().setHeader(Client.REQUEST_CONTEXT , requestContext);
exchange.getIn().setHeader(CxfConstants.OPERATION_NAME, GREET_ME_OPERATION);
}
});
The above example has some stuff we don't need, but the important thing is that it shows us how to set the CXF Request Context.
Put them together and you get:
#Override
public void process(Exchange exchange) throws Exception {
AttributedURIType messageIDAttr = new AttributedURIType();
messageIDAttr.setValue("customValue");
AddressingProperties maps = new AddressingProperties();
maps.setMessageID(messageIDAttr);
Map<String, Object> requestContext = new HashMap<>();
requestContext.put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES, maps);
exchange.getIn().setHeader(Client.REQUEST_CONTEXT, requestContext);
}
// org.apache.cxf.ws.addressing.JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES = "javax.xml.ws.addressing.context"
// org.apache.cxf.endpoint.Client.REQUEST_CONTEXT = "RequestContext"
Warning: In my route, I invoke multiple different web services sequentially. I discovered that after setting the RequestContext as shown above, Camel started using the same RequestContext for all web services, which resulted in an error: "A header representing a Message Addressing Property is not valid and the message cannot be processed". This is because the incorrect "Action" header was used for all web service invocations after the first.
I traced this back to Apache Camel using a "RequestContext" Exchange property, separate from the header we set, which apparently takes priority over the header. If I remove this property prior to calling subsequent web services, CXF automatically fills in the correct Action header.
if your problem not solved, I suggest you to combine your cxf service with custom interceptor. it easy to work with your soap message. like this:
<bean id="TAXWSS4JOutInterceptorBean" name="TAXWSS4JOutInterceptorBean" class="com.javainuse.beans.SetDetailAnswerInterceptor " />
<cxf:cxfEndpoint id="CXFTest" address="/javainuse/learn"
endpointName="a:SOATestEndpoint" serviceName="a:SOATestEndpointService"
serviceClass="com.javainuse.SOATestEndpoint"
xmlns:a ="http://javainuse.com">
<cxf:binding>
<soap:soapBinding mtomEnabled="false" version="1.2" />
</cxf:binding>
<cxf:features>
<wsa:addressing xmlns:wsa="http://cxf.apache.org/ws/addressing"/>
</cxf:features>
<cxf:inInterceptors>
<ref bean="TAXWSS4JInInterceptorBean" />
</cxf:inInterceptors>
<cxf:inFaultInterceptors>
</cxf:inFaultInterceptors>
<cxf:outInterceptors>
<ref bean="TAXWSS4JOutInterceptorBean" />
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
</cxf:outFaultInterceptors>
</cxf:cxfEndpoint>
and in the interceptor you can set soap headers like this:
public class SetDetailAnswerInterceptor extends WSS4JOutInterceptor {
public SetDetailAnswerInterceptor() {
}
#Override
public void handleMessage(SoapMessage mc) {
AttributedURIType value = new AttributedURIType();
value.setValue("test");
((AddressingProperties) mc.get("javax.xml.ws.addressing.context.outbound")).setMessageID(value);
}
}

How to test wcf soap service using POSTMAN?

Hi I am developing XML Soap services using WCF. My requirement is to update some database table. I have one method to update values in the db. Below is my service.
[ServiceContract]
public interface IOpportunity
{
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Xml, UriTemplate = "postmethod/updateOpportunity")]
bool updateOpportunity(opportunityActivity obj);
}
[DataContract]
public class opportunityActivity
{
[DataMember]
public string opportunityID { get; set; }
[DataMember]
public string opportunityStatus { get; set; }
[DataMember]
public string opportunityserviceType { get; set; }
}
Below is my xml.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:s="http://localhost:39512/Opportunity.svc">
<soapenv:Header/>
<soapenv:Body>
<s:request>
<opportunityID>1-1D5SJX</opportunityID>
<opportunityStatus>Completed</opportunityStatus>
<opportunityserviceType>LEASE_REQUEST</opportunityserviceType>
</s:request>
</soapenv:Body>
</soapenv:Envelope>
when i i try as shown above i get 400 bad request error.May i know am i following correct approach to test the service? Can someone correct me if i am doing wrong? Any help would be greatly appreciated. Thank you.
You have to pass a soap message to the service endpoint.
Eg
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:s="http://myNamespace/">
<soapenv:Header/>
<soapenv:Body>
<s:request>
....
</s:request>
</soapenv:Body>
</soapenv:Envelope>
To get hold of a soap message you should use the service endpoint definition and use some tooling to generate a valid request.
Additionally, you should not be sending data to the endpoint address with ?wsdl as part of the address. It should only be the endpoint address.

Empty soap envelope in WSO2 axis2 module

I'm working on custom axis2 module for wso2 esb. Right now I'm using code from https://docs.wso2.com/display/ESB490/Writing+an+Axis2+Module
and I have a problem with incoming requests. It doesn't matter what request I send because it always looks like this:
<?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body/></soapenv:Envelope>
On the other hand OutFlow works more or less as it should - response looks ok but instead of "out" its direction is set as "in". If I'm not mistaken invoke method will be called for requests and revoke for responses - am I right? In my case both are using invoke. Any ideas what I'm doing wrong?
Edit:
My handler code:
public class LogHandler extends AbstractHandler implements Handler {
private Logger log = Logger.getLogger(LogHandler.class.toString());
#Override
public void init(HandlerDescription handlerDescription) {
super.init(handlerDescription);
}
public InvocationResponse invoke(MessageContext msgContext) throws AxisFault {
System.out.println("invoked: " + msgContext.getEnvelope().toString() + "\n");
log.info("invoked: " + msgContext.getEnvelope().toString() + "\n");
return InvocationResponse.CONTINUE;
}
public void revoke(MessageContext msgContext) {
log.info("revoked: " + msgContext.getEnvelope().toString() + "\n");
}
}
Module:
public class LoggingModule implements Module {
private static final Log log = LogFactory.getLog(LoggingModule.class);
// initialize the module
public void init(ConfigurationContext configContext, AxisModule module) throws AxisFault {
}
public void engageNotify(AxisDescription axisDescription) throws AxisFault {
}
// shutdown the module
public void shutdown(ConfigurationContext configurationContext) throws AxisFault {
}
public String[] getPolicyNamespaces() {
return null;
}
public void applyPolicy(Policy policy, AxisDescription axisDescription) throws AxisFault {
}
public boolean canSupportAssertion(Assertion assertion) {
return true;
}
}
module.xml:
<module name="sample-logging" class="pl.wso2.logging.LoggingModule">
<InFlow>
<handler name="InFlowLogHandler" class="pl.wso2.logging.LogHandler">
<order phase="loggingPhase"/>
</handler>
</InFlow>
<OutFlow>
<handler name="OutFlowLogHandler" class="pl.wso2.logging.LogHandler">
<order phase="loggingPhase"/>
</handler>
</OutFlow>
</module>
In my wso2 proxy I use Payload Mediator to create response and then return it using Respond Mediator.
For given request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<aa>blahblah</aa>
</soapenv:Body>
</soapenv:Envelope>
there two thing logged:
request from InFlow
invoked: <?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlso
ap.org/soap/envelope/"><soapenv:Body/></soapenv:Envelope>
and response from OutFlow
invoked: <?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlso
ap.org/soap/envelope/"><soapenv:Body><m:checkpriceresponse xmlns:m="http://services.samples/xsd"><m:
code>dsadsa</m:code></m:checkpriceresponse></soapenv:Body></soapenv:Envelope>
As per https://axis.apache.org/axis2/java/core/docs/modules.html#Step2_:_LogHandler ...
"public void invoke(MessageContext ctx);" is the method that is called
by the Axis2 engine when the control is passed to the handler. "public
void revoke(MessageContext ctx);" is called when the handlers are
revoked by the Axis2 engine."
which means since you are calling same handler in both InFlow and OutFlow the same invoke() method should be getting triggered for both the request and the response. If you want different logics to be executed for requests and responses maybe you should write separate handlers for request and response.
After debugging everything I've found that while request was parsed in InFlow, instead of using its soap message, new one (empty) was created. Thankfully it's possible to access proper request using soap tracer handler (or just its code).

Can't find SOAP headers after spring integration marshalling

I'm trying to create an inbound gateway for a SOAP service, that accepts SOAP requests like the following:
<?xml version="1.0" ?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<RequestHeader xmlns="http://test.com/">
<SecurityToken>mytoken</SecurityToken>
<RequestID>1234</RequestID>
</RequestHeader>
</S:Header>
<S:Body>
<BaseRequest xmlns="http://test.com/">
<RequestData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="BalanceRequest">
<CustomerID>1234</CustomerID>
</RequestData>
</BaseRequest>
</S:Body>
</S:Envelope>
I want to use JAXB for marshalling/unmarshalling the request/response. I have managed to configure Spring/Spring-integration with the following:
<oxm:jaxb2-marshaller id="soapMarshaller" context-path="com.test" />
<int:channel id="soap-channel"/>
<int-ws:inbound-gateway id="ws-inbound-gateway"
request-channel="soap-channel"
marshaller="soapMarshaller"/>
<int:service-activator input-channel="soap-channel">
<bean class="com.test.SoapServiceActivator"/>
</int:service-activator>
And I have tried to extract the SOAP header and body in the service activator.
#ServiceActivator
public BaseResponse issueResponseFor(BaseRequest body,
#Headers Map<String, Object> headerMap) {
return null;
}
BaseRequest is a JAXB annotated class.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"requestData"
})
#XmlRootElement(name = "BaseRequest")
public class BaseRequest {
#XmlElement(name = "RequestData")
protected BaseRequestBody requestData;
}
My problem is that in the variable body, I get the body of the SOAP request, but I didn't find anyway to extract the headers of the SOAP request. The headerMap variable holds only the standard Spring-Integration headers (replyChannel, errorChannel, id, timestamp). By headers, I mean the SecurityToken+RequestID, and also the HTTP header with the name of the requested action.
Any idea how to do that ?
Try to use mapped-request-headers="*".
By default the the DefaultSoapHeaderMapper maps only standard headers. And in this case it is only WebServiceHeaders.SOAP_ACTION
UPDATE
Quoting Omar :-)
Thanks ! The following code is working great :
#Autowired
#Qualifier("soapMarshaller")
Jaxb2Marshaller marshaller;
#ServiceActivator
public BaseResponse issueResponseFor(BaseRequest request, #Header("RequestHeader") SoapHeaderElement soapHeader) {
BaseRequestHeader requestHeader = (BaseRequestHeader) JAXBIntrospector.getValue(marshaller.unmarshal(soapHeader.getSource()));`

How to unmarshall to different #RequestBody object types?

I'm using Spring in my web service which receives XML as input. It can be XML embebed in the HTTP request or as a plain text in the request attribute.
Currently my web service is handling two different XML schemas so my unmarshaller can unmarshall the XML files to two object types (for example: Foo and Bar).
In my Controller, I have the next code to handler the request attribute:
#RequestMapping(value={"/mypath"}, method={RequestMethod.POST}, headers={"content-type=application/x-www-form-urlencoded"})
#ResponseBody
public ResponseObject getResponse(#RequestParam("request") String request, HttpServletRequest req) {
It works perfectly, with the request string I can unmarshall to Foo object or Bar object.
The problem comes with the XML embebed:
#RequestMapping(value={"/mypath"}, method={RequestMethod.POST}, headers={"content-type=text/xml"})
#ResponseBody
public ResponseObject getResponse(#RequestBody Foo request, HttpServletRequest req) {
and
#RequestMapping(value={"/mypath"}, method={RequestMethod.POST}, headers={"content-type=text/xml"})
#ResponseBody
public ResponseObject getResponse(#RequestBody Bar request, HttpServletRequest req) {
and here is the MessageConverter:
<bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="jaxb2Marshaller" />
<property name="unmarshaller" ref="jaxb2Marshaller" />
</bean>
<oxm:jaxb2-marshaller id="jaxb2Marshaller" contextPath="path.to.Foo:path.to.Bar"/>
I think that the MessageConverter should do the unmarshall automagically but I receive the next error:
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path '/ws/mypath.ws': [...] If you intend to handle the same path in multiple methods, then factor them out into a dedicated handler class with that path mapped at the type level!
How can I unmarshall automatically to different #RequestBody object types? (with the same web service path)
There has to be something in the #RequestMapping which makes each request method unique, in your case both the xml based request mappings exactly the same - the type of the parameters is figured out after the framework has found the correct method with the #RequestMapping. So essentially what you are saying is not feasible unless you have something more in the annotation to help the framework with finding the correct method.
One small simplification that you can make is the following, if you are on Spring 3.1+:
#RequestMapping(value={"/mypath"}, method={RequestMethod.POST}, consumes=text/xml)