There is this legacy system which is sending SOAP requests with an empty namespace attribute causing fields values to be null, when namespace attribute is removed while testing from SOAP UI, it work fine.
This is what problematic SOAP request looks like
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ADD xmlns="http://www.cc.com/ws">
<ID xmlns="">dummy</ID>
<PWD xmlns="">password</PWD>
</ADD>
</soapenv:Body>
</soapenv:Envelope>
I tried by adding an interceptor using addInterceptors method of WsConfigurerAdapter, code of which is pasted below, but it doesn't work
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
SOAPMessage soapMessage = ((SaajSoapMessage) messageContext.getRequest()).getSaajMessage();
SOAPBody body = soapMessage.getSOAPBody();
Iterator<SOAPBodyElement> it = body.getChildElements();
while (it.hasNext()) {
Object node = it.next();
if (!isTextNode(node)) {
com.sun.xml.internal.messaging.saaj.soap.ver1_1.BodyElement1_1Impl e =
(com.sun.xml.internal.messaging.saaj.soap.ver1_1.BodyElement1_1Impl) node;
Iterator<SOAPBodyElement> subItr = e.getChildElements();
while(subItr.hasNext()) {
Object subNode = subItr.next();
if(subNode instanceof com.sun.xml.internal.messaging.saaj.soap.impl.ElementImpl) {
SOAPElement el = (SOAPElement) subNode;
el.removeAttribute("xmlns");
}
}
}
}
soapMessage.writeTo(System.out);
return true;
}
Note that a namespace declaration in XML is not an attribute. Don't be confused by the similar syntax!
So I would not expect e.removeAttribute to do anything, because there is no attribute with the name xmlns.
What you actually need to do is to change the name of the element, e.g. using its setElementQName() method.
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
SOAPMessage soapMessage = ((SaajSoapMessage) messageContext.getRequest()).getSaajMessage();
SOAPBody body = soapMessage.getSOAPBody();
Iterator<SOAPBodyElement> it = body.getChildElements();
while (it.hasNext()) {
Object node = it.next();
if (!isTextNode(node)) {
com.sun.xml.internal.messaging.saaj.soap.ver1_1.BodyElement1_1Impl e =
(com.sun.xml.internal.messaging.saaj.soap.ver1_1.BodyElement1_1Impl) node;
Iterator<SOAPBodyElement> subItr = e.getChildElements();
while(subItr.hasNext()) {
Object subNode = subItr.next();
if(subNode instanceof com.sun.xml.internal.messaging.saaj.soap.impl.ElementImpl) {
SOAPElement el = (SOAPElement) subNode;
el.setElementQName(
new QName(
e.getElementName().getURI(),
el.getElementName().getLocalName()
)
);
}
}
}
}
soapMessage.writeTo(System.out);
return true;
}
Related
I am able to send requests to the web service using javax.xml.soap.*, I would like to covert the code to use webServiceTemplate.
I am struggling with creating request and result objects. (sample Ive found is related to xml not SOAP)
I am also wondering if there is any advantages of using
webServiceTemplate over java.xml.soap. If there is not am I doing it correctly? Given that I need to get connected to 20 web services.
The only service it has is findEvents as follows:
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://ticketmaster.productserve.com/v2/soap.php" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Header/>
<soapenv:Body>
<soap:findEvents soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<request xsi:type="soap:Request">
<!--You may enter the following 7 items in any order-->
<apiKey xsi:type="xsd:string">?</apiKey>
<country xsi:type="xsd:string">?</country>
<resultsPerPage xsi:type="xsd:int">?</resultsPerPage>
<currentPage xsi:type="xsd:int">?</currentPage>
<sort xsi:type="soap:Request_Sort">
<!--You may enter the following 2 items in any order-->
<field xsi:type="xsd:string">?</field>
<order xsi:type="xsd:string">?</order>
</sort>
<filters xsi:type="soap:ArrayOfRequest_Filter" soapenc:arrayType="soap:Request_Filter[]"/>
<updatedSince xsi:type="xsd:string">?</updatedSince>
</request>
</soap:findEvents>
</soapenv:Body>
</soapenv:Envelope>
My code is as follows:
try {
SOAPConnectionFactory soapConnectionFactory =
SOAPConnectionFactory.newInstance();
SOAPConnection connection =
soapConnectionFactory.createConnection();
MessageFactory factory =
MessageFactory.newInstance();
SOAPMessage message = factory.createMessage();
SOAPHeader header = message.getSOAPHeader();
header.detachNode();
SOAPBody body = message.getSOAPBody();
SOAPFactory soapFactory =
SOAPFactory.newInstance();
Name bodyName;
bodyName = soapFactory.createName("findEvents",
"xsd", "http://ticketmaster.productserve.com/v2/soap.php");
SOAPBodyElement getList =
body.addBodyElement(bodyName);
Name childName = soapFactory.createName("findEvents");
SOAPElement eventRequest = getList.addChildElement(childName);
childName = soapFactory.createName("apiKey");
SOAPElement apiKey = eventRequest.addChildElement(childName);
apiKey.addTextNode("MYAPI");
childName = soapFactory.createName("country");
SOAPElement cid = eventRequest.addChildElement(childName);
cid.addTextNode("UK");
message.writeTo(System.out); //show message details
URL endpoint = new URL("http://ticketmaster.productserve.com/v2/soap.php");
SOAPMessage response =
connection.call(message, endpoint);
connection.close();
//SOAPBody soapBody = response.getSOAPBody();
SOAPMessage sm = response;
System.out.println("Response:");
ByteArrayOutputStream out = new ByteArrayOutputStream();
sm.writeTo(out);
String validSoap = "<?xml version=\"1.0\"?> " + out.toString();
System.out.println("It is ValidSoap: " + validSoap); //ValidSoap message
SAXBuilder builder = new SAXBuilder();
Reader in = new StringReader(validSoap); //reading character stream
Document doc = null; //empty jDom document is instantiated
doc = builder.build(in); //build the jDom document
Element root = doc.getRootElement(); //Envelope
List allChildren = root.getChildren(); //list of all its child elements
System.out.println("Root is:" + ((Element) allChildren.get(0)).getName());
listChildren(root);
} catch (Exception ex) {
ex.printStackTrace();
}
New Code
webServiceTemplate.sendSourceAndReceiveToResult
("http://ticketmaster.productserve.com/v2/soap.php",source, result);
#XmlRootElement
public class FindEvents {
#XmlElement
Request request;
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
}
#XmlSeeAlso(SortTicket.class)
public class Request {
#XmlElement
String apiKey;
#XmlElement
String country;
#XmlElement
int resultsPerPage;
#XmlElement
int currentPage;
#XmlElement(name = "Sort")
SortTicket sort;
#XmlElement
String[] filters;
#XmlElement
String updatedSince;
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public int getResultsPerPage() {
return resultsPerPage;
}
public void setResultsPerPage(int resultsPerPage) {
this.resultsPerPage = resultsPerPage;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public SortTicket getSort() {
return sort;
}
public void setSort(SortTicket sort) {
this.sort = sort;
}
public String[] getFilters() {
return filters;
}
public void setFilters(String[] filters) {
this.filters = filters;
}
public String getUpdatedSince() {
return updatedSince;
}
public void setUpdatedSince(String updatedSince) {
this.updatedSince = updatedSince;
}
}
public class SortTicket {
#XmlElement
String field;
#XmlElement
String order;
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
}
Since you have generated DTO classes with Jaxb annotation you can create a marshaller ,unmarshaller and create objects of the DTO classes (SortTicket,Request,FindEvents) and send the objects directly instead of using the xml request
webServiceTemplate.marshalSendAndReceive(findEvents);
Something like this you'll have to configure.
Create a marshaller
<oxm:jaxb2-marshaller id="marshaller" contextPath="com.yourcontextpath" />
create web service template
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
<property name="defaultUri"
value="http://ticketmaster.productserve.com/v2/soap.php" />
</bean>
and in some class's method where you want to send soap request inject webServiceTemplate using #Autowired
#Autowired
private WebServiceTemplate webServiceTemplate;
public void sendSampleSoapRequest() {
SortTicket sortTicket=new SortTicket();
// set its values
Request request=new Request();
//set its values
request.setSort(sortTicket);
FindEvents findEvents=new FindEvents();
setRequest(request)
Object response=webServiceTemplate.marshalSendAndReceive(findEvents);
}
marshalSendAndReceive message uses the Jaxb marshaller to convert your objects (marked with JaxB annotation)to xml.So above your findEvents object will be converted to its xml from.
Regarding your second point
Advantages of using webServiceTemplate over java.xml.soap. : you don't have to create those SOAPElements manually you just create an object and send it instead of big code for manually handling it.
Since you'll have to connect to 20 different web services it will be much easier for you to create DTO objects and send them directly.You may need to modify my above samples a little.May remove the deault uri
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
</bean>
and while sending request give the URI request
Object response=webServiceTemplate.marshalSendAndReceive(uri,object);
For sending it to multiple server
Object response1=webServiceTemplate.marshalSendAndReceive(uri1,object);
Object response1=webServiceTemplate.marshalSendAndReceive(uri2,object)
uri1 and uri2 can be different soap service And if you don't have the wsdl you can send xml with this method
sendSourceAndReceiveToResult(uri1,source, result);
sendSourceAndReceiveToResult(uri2,source, result);
Sending a uri in the send method over rides the default URI
For example check this also check the api doc
When I add soap headers from handler.handlResponse(), I can see the headers added in the handler but these headers do not make it to the client.
Here is my handleResponse() method.
public static final String WEB_SERVICE_NAMESPACE_PREIFX = "dm";
public static final String WEB_SERVICE_NAMESPACE_URI = "urn:com.qwest.dms.dto";
public boolean handleResponse(MessageContext context)
{
logger.debug("TransactionLoggerHandler.handleResponse invoked");
try
{
SOAPMessageContext soapContext;
soapContext = (SOAPMessageContext)context;
SOAPMessage message = soapContext.getMessage();
SOAPHeader soapHeader = message.getSOAPHeader();
String version = "version";
SOAPHeaderElement header;
SOAPFactory soapFactory;
Name name;
logger.debug("Adding soap header ["+version+"] with value [2.0].");
soapHeader.addNamespaceDeclaration(Constants.WEB_SERVICE_NAMESPACE_PREIFX, Constants.WEB_SERVICE_NAMESPACE_URI)
SOAPHeaderElement headerElement
= (SOAPHeaderElement)message.getSOAPPart().getEnvelope().getHeader().addChildElement(
"version",
Constants.WEB_SERVICE_NAMESPACE_PREIFX,
Constants.WEB_SERVICE_NAMESPACE_URI );
headerElement.addTextNode("2.0");
String headerName="protocol";
String headerValue="2.0.0";
logger.debug("Adding soap header ["+headerName+"] with value ["+headerValue+"].");
soapFactory = SOAPFactory.newInstance();
name = soapFactory.createName(headerName,
Constants.WEB_SERVICE_NAMESPACE_PREIFX,
Constants.WEB_SERVICE_NAMESPACE_URI );
header = soapHeader.addHeaderElement( name );
header.addTextNode(headerValue);
message.saveChanges();
DmsUtil.printSOAPMessage(message);
logger.debug("Soap header ["+version+"] with value [2.0] added.");
}
catch (Exception e)
{
logger.error(e);
}
return true;
}
I see the output from this method as the following:
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header xmlns:dm="urn:com.qwest.dms.dto">
<dm:version>2.0</dm:version>
<dm:protocol>2.0.0</dm:protocol>
</env:Header>
<env:Body>
From the Client i get the following:
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
</env:Envelope>
I am not sure why these headers not sent over the wire. Any help is appreciated. BTW, I am using jax rpc webservices under jboss4 (I know, i have to upgrade this but can not due to some constraints :( ).
I used the Netbeans Web Service wizard to generate Java code given a WSDL. If I drag the web service method into a class, then it creates some Java code to call that web service (ex: SubmitApplication). I can see how to populate objects to send info to that web service, but the service also requires a security header with username/password.
There is a generated class called SecurityHeader that contains the username/password attributes. I can create this object with a valid username/password, but I cannot see how to pass that object or add it to the SubmitApplication call. How can the SecurityHeader be added to the SubmitApplication call?
Here is an example of what the SOAP request should look like:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<SecurityHeader xmlns="http://schemas.turss.com/BDS/1.0/">
<CreateTime>6/8/2012 8:32:59 PM</CreateTime>
<Owner>Sample_Owner</Owner>
<HashKey>Sample_Hash_Key</HashKey>
</SecurityHeader>
</soap:Header>
<soap:Body>
<SubmitApplication xmlns="http://schemas.turss.com/BDS/1.0/">
<newSearch>
<CurrentApplicant xmlns="http://schemas.turss.com/BDS/1.0/proxy">
<FirstName>Bob</FirstName>
<MiddleName />
<LastName>Smith</LastName>
<Suffix />
<BirthDate>1970-10-20T00:00:00</BirthDate>
<SSN />
<Address />
<City />
<State />
<PostalCode />
</CurrentApplicant>
<PermissiblePurpose xmlns="http://schemas.turss.com/BDS/1.0/proxy">TenantScreening</PermissiblePurpose>
</newSearch>
</SubmitApplication>
</soap:Body>
</soap:Envelope>
I found a sample code that add a token string to a soap security header. Here is the header form of the code below :
<TicketHeader>
<Ticket>OD01096347CCA</Ticket>
</TicketHeader>
The method to add the header to the message :
// Security token
String token;
// MyService and MySoapService are stubs generated from WSDL
MyService service = new MyService();
MyServiceSoap ep = service.getMyServiceSoap();
Binding binding = ((BindingProvider) ep).getBinding();
List handlers = binding.getHandlerChain();
handlers.add(new MySOAPHandler(token));
binding.setHandlerChain(handlers);
code of MySoapHandler :
public class MySOAPHandler implements SOAPHandler {
private String token;
public DHSOAPHandler(String token) {
this.token = token;
}
public boolean handleMessage(SOAPMessageContext messageContext) {
SOAPMessage
msg = messageContext.getMessage();
if ((Boolean) messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)){
try {
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.addHeader();
SOAPElement el = header.addHeaderElement(envelope.createName("TicketHeader",
"", "http://ws.service.com/"));
el = el.addChildElement(envelope.createName("Ticket", "", "http://ws.service.com/"));
el.setValue(token);
msg.saveChanges();
}
catch (SOAPException e) {
return false;
}
}
return true;
}
public boolean handleFault(SOAPMessageContext messageContext) {
return true;
}
public void close(MessageContext messageContext){
}
// I'm not quite sure about what should this function do, but I guess something like this...
public Set getHeaders(){
Set headers = new HashSet();
headers.add(new QName("https://ws.service.com/", "TicketHeader"));
return headers;
}
}
Here is the request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soap="http://soap.ws.server.wst.fit.cvut.cz/">
<soapenv:Header>
<userId>someId</userId>
</soapenv:Header>
<soapenv:Body>
...
</soapenv:Body>
</soapenv:Envelope>
and I want to get that userId.
I tried this
private List<Header> getHeaders() {
MessageContext messageContext = context.getMessageContext();
if (messageContext == null || !(messageContext instanceof WrappedMessageContext)) {
return null;
}
Message message = ((WrappedMessageContext) messageContext).getWrappedMessage();
return CastUtils.cast((List<?>) message.get(Header.HEADER_LIST));
}
private String getHeader(String name) {
List<Header> headers = getHeaders();
if (headers != null) {
for (Header header : headers) {
logger.debug(header.getObject());
// return header by the given name
}
}
return null;
}
And it logs [userId : null]. How can I get the value and why is null there?
"[userId : null]" is generally the "toString" printout of a DOM element. Most likely if you do something like
logger.debug(header.getObject().getClass())
you will see that it is a DOM Element subclass of somesort. Thus, something like:
logger.debug(((Element)header.getObject()).getTextContent())
might print the text node.
import javax.xml.soap.*;
SOAPPart part = request.getSOAPPart();
SOAPEnvelope env = part.getEnvelope();
SOAPHeader header = env.getHeader();
if (header == null) {
// Throw an exception
}
NodeList userIdNode = header.getElementsByTagNameNS("*", "userId");
String userId = userIdNode.item(0).getChildNodes().item(0).getNodeValue();
You can get soap headers without Interceptors and without JAXB.
In your service_impl class add :
public class YourFunctionNameImpl implements YourFunctionName{
#Resource
private WebServiceContext context;
private List<Header> getHeaders() {
MessageContext messageContext = context.getMessageContext();
if (messageContext == null || !(messageContext instanceof WrappedMessageContext)) {
return null;
}
Message message = ((WrappedMessageContext) messageContext).getWrappedMessage();
List<Header> headers = CastUtils.cast((List<?>) message.get(Header.HEADER_LIST));
return headers;
}
...
Then in your function you can use:
List<Header> headers = getHeaders();
for(Iterator<Header> i = headers.iterator(); i.hasNext();) {
Header h = i.next();
Element n = (Element)h.getObject();
System.out.println("header name="+n.getLocalName());
System.out.println("header content="+n.getTextContent());
}
We can get SOAP header in server side by adding following code in CXF interceptor.
Create a class like
public class ServerCustomHeaderInterceptor extends AbstractSoapInterceptor {
#Resource
private WebServiceContext context;
public ServerCustomHeaderInterceptor() {
super(Phase.INVOKE);
}
#Override
public void handleMessage(SoapMessage message) throws Fault,JAXBException {
System.out.println("ServerCustomHeaderInterceptor handleMessage");
JAXBContext jc=null;
Unmarshaller unmarshaller=null;
try {
jc = JAXBContext.newInstance("org.example.hello_ws");
unmarshaller = jc.createUnmarshaller();
} catch (JAXBException e) {
e.printStackTrace();
}
List<Header> list = message.getHeaders();
for (Header header : list) {
ElementNSImpl el = (ElementNSImpl) header.getObject();
ParentNode pn= (ParentNode) el.getFirstChild();
//Node n1= (Node) pn;
//Node n1= (Node) el.getFirstChild();
CustomHeader customHeader=(CustomHeader) unmarshaller.unmarshal(el.getFirstChild());
}
}
After this we need to inject this as a interceptor like
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean class="org.example.hellows.soap12.ServerCustomHeaderInterceptor" />
</jaxws:inInterceptors>
in your server context xml.
We may need to change few lines as per your requirements. Basic flow will work like this.
Having a MessageContext messageContext, you can use this code:
HeaderList hl = (HeaderList) messageContext.get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY);
which gives you access to all SOAP headers.
I'm trying to create a generic web service that will always respond with "OK", regardless of the request's header or body contents. I can do this in Axis2 with a RawXMLInOutMessageReceiver, but I'd prefer to use JAX-WS (which I am completely new to) if at all possible. So far I've got a simple interface:
#WebService
public interface DummyService {
#WebMethod String processMessage(Object obj);
}
and a simple implementaion:
#WebService(endpointInterface = "com.dummyservice.DummyService")
public class DummyServiceImpl implements DummyService {
#Override
public String processMessage(Object obj) {
return "OK";
}
}
I can successfully publish the service with javax.xml.ws.Endpoint#publish(...), but when I hit it with a simple SOAP request, e.g.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<derp/>
</soapenv:Body>
</soapenv:Envelope>
I'm greeted with a SOAPFault stating Cannot find dispatch method for {}derp.
Is it even possible to create a generic/dumb web service that will ACK everything with JAX-WS? If so, could someone point me in the right direction?
EDIT
Thanks to the tip from McDowell, I was able to do this with a SOAPHandler:
public class DummySOAPHandler implements SOAPHandler {
#Override
public boolean handleMessage(MessageContext context) {
return process((SOAPMessageContext) context);
}
#Override
public boolean handleFault(MessageContext context) {
return process((SOAPMessageContext) context);
}
#Override
public void close(MessageContext context) { }
#Override
public Set<QName> getHeaders() {
return null;
}
private boolean process(SOAPMessageContext ctx) {
try {
SOAPMessage message = ctx.getMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPBody body = message.getSOAPBody();
if ((Boolean) ctx.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY)) {
Iterator<SOAPElement> bodyChildren = body.getChildElements();
while (bodyChildren.hasNext()) {
SOAPElement child = bodyChildren.next();
child.detachNode();
}
body.addBodyElement(envelope.createName("OK"));
message.saveChanges();
}
} catch (SOAPException e) {
e.printStackTrace();
}
return true;
}
}
I expect your service is expecting something of the form:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:dum="http://yournamespace/">
<soapenv:Header/>
<soapenv:Body>
<dum:processMessage>
<!-- xsd:anyType -->
</dum:processMessage>
</soapenv:Body>
</soapenv:Envelope>
Add ?WSDL to your endpoint and inspect the operation input XML type and the namespaces.
You might be able to do something with a logical handler (javadoc) to transform the incoming request to this form - I haven't tried.