How to trigger a handler class when consuming web-service in java - java

Using Axis 1.4 I built client application that will consume external server services.
The server application response with soap message that include header tag along with body tag.
My problem with the header tag, I am trying to find away to get the header element.
What is done so far:
I found that I need to use a handler that extends BasicHandler using this class I can get the header tag. source: Dealing with SOAP Headers in Axis
But how to make this handler work when consuming web-service? I mean how to invoke this handler when ever I am receiving response from server to get its header.
Some blogs suggest I need to use .wsdd file. I am using Jdeveloper 11g with weblogic 10.3.6 environment where I am only aware of web.xml file for configuration.
Question: How to link those information(handler class, .wsdd file and web.xml) to gather and make the handler works to get the header tags?

The best start was to check Axis guide on: Apache-Axis Reference Guide where you will have an overview of the work flow.
To configure handlers to be trigger from the client side you need to do the following:
1- Create handler class basically something similar to the following:
package mypackge;
import javax.xml.soap.SOAPException;
import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.apache.axis.handlers.BasicHandler;
import org.apache.axis.message.SOAPHeader;
import org.apache.axis.message.SOAPHeaderElement;
public class SoapHeaderConsumerHandler
extends BasicHandler
{
public void invoke(MessageContext messageContext)
throws AxisFault
{
// Your logic for request or response handling goes here.
// Basically you need to make use of the parameter
// messageContext where you can access the soap header and body tags.
}
}
2- Create the client-config.wsdd file. it will look like the following:
<deployment
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<globalConfiguration>
<responseFlow>
<handler name="log" type="java:mypackge.SoapHeaderConsumerHandler"/>
</responseFlow>
</globalConfiguration>
<transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/>
</deployment>
You can see that I am using only handlers for the incoming response from the server side. So when ever the client application receive a response from the server the handler class SoapHeaderConsumerHandler will be triggered and the method invoke will be called by default.
Note: if you want to access the outgoing request before send it to the server you need to add extra tag for <requestFlow> to add request handler.
Check Deployment(WSDD) Reference from Axis guide:
3- Where to place the client-config.wsdd file ?
You should place the .wsdd file in the working directory. You can easily find out the working directory location using :
System.out.println("Working Directory = " + System.getProperty("user.dir"));
Source: Getting the Current Working Directory in Java
UPDATE:
I found out that it is not necessary to put the client-config.wsdd file in the working directory. You can specify the path of this file in your code as follow:
System.setProperty("axis.ClientConfigFile", "[Path goes here]\\client-config.wsdd");
You just need to place the .wsdd file there.
Extra Useful Links:
Where to place the client-config.wsdd file in Railo
V Axis handler This is an example for the server side handlers.
Dealing with SOAP Headers in Axis

To avoid file location problems you can programatically configure axis :
ConsultationServiceLocator stub = new ConsultationServiceLocator();
SimpleProvider clientConfig = new SimpleProvider();
SoapHeaderConsumerHandler logHandler = new SoapHeaderConsumerHandler();
SimpleChain reqHandler = new SimpleChain();
SimpleChain respHandler = new SimpleChain();
reqHandler.addHandler(logHandler);
respHandler.addHandler(logHandler);
Handler pivot = new HTTPSender();
Handler transport = new SimpleTargetedChain(reqHandler, pivot, respHandler);
clientConfig.deployTransport(HTTPTransport.DEFAULT_TRANSPORT_NAME, transport);

Related

Java Spring Webservices SOAP Authentication

I am using java spring webservice to send soap requests to a server. However I get an error from the server saying
The security context token is expired or is not valid
So then I added authentication to the SOAP headers, resulting in request xml something like this:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header>
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<UsernameToken>
<Username>XXXXXXXX</Username>
<Password>XXXXXXXX</Password>
</UsernameToken>
</Security>
</env:Header>
<env:Body>
The body is in the correct format here
</env:Body>
</env:Envelope>
But I still get that error. From what I understand, the client sends authentication credentials to the server, the server sends back requestToken which is used to keep connection alive between client and server and then client using the token received from the server can make any other API calls such as login, buy, sell (or whatever mentioned in API)
Am I right in this assumption? If yes, how can this be implemented using Java Spring WebServices. Do I need to generate fields on client side like BinarySecret and package that under RequestSecurityContext?
For adding the SOAP headers, I wrote a class which implements WebServiceMessageCallback and overrides doWithMessage method and writes headers in that for username and password (i.e security)
Any help would be appreciated! Thank You!
So after 2 days of finding out, I was able to figure out the solution!
1) Generate the classes from wsdl using CXF wsdl2java
2) Make sure in the pom file, you point to the correct wsdlLocation
3) Get the stub from the service class generated and inject username and password provided and then it should work. Something like this:
final YourService service = new YourService();
final YourStub stub = service.getService();
final Map ctx = ((BindingProvider)stub).getRequestContext();
ctx.put("ws-security.username", userName);
ctx.put("ws-security.password", password);
stub.callYourMethod();
PS: Please make sure you have the right libraries, I just used cxf-bundle and nothing else from cxf and it worked! Earlier it was not working as I had individually included libraries from cxf.

CXF file upload service: Couldn't determine the boundary from the message

I am prototyping a very simple POST service to upload a file:
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Path("fileUpload")
public Response uploadFile(MultipartBody body) {
//never gets to here
System.out.println("In uploadFile");
//TODO: implementation
return Response.ok().build();
}
I get
org.apache.cxf.interceptor.Fault: Couldn't determine the boundary from the message!
I also tried to replace the method declaration with:
public Response uploadFile(List<Attachment> attachments,
#Context HttpServletRequest request) {
as per some Google findings, to no help.
I am using two different clients to invoke this service: chrome://poster, which has a field to include a file attachment, and a simple Python script as is explained here. Both clients produce the same result.
How should I change my service implementation or the call or both in order to be able to pass the CXF validation and enter into the actual body of the service?
REFERENCES:
JAX-RS : Support for Multiparts
Apache CXF: JAX-RS Restful web service for uploading/downloading Text file + Java client
The server side code looks fine. Problem is the way you are sending data from client. You are sending data as a stream in payload not as an attachment which has boundary. To verify quickly you can enable logging request and response by enabling CXF Feature LoggingFeature or Interceptors LoggingInInterceptor and LoggingoutInterceptor. In the log if you see data coming Payload then you are sending data as stream in this case you need to change the way you send the data else you can change consumes to application/octetstream and receive data using inputstream directly.
I'm not aware of the tool you are using, however I use Chrome Extension to postman to test the REST services. If you install the extension and launch the application.
You can upload the file using below approach.
Change Method type to POST from the drop down.
Enter the URL
Select Tab Body
Select Form-Data Radio Button
On the right most row select file from drop down. as shown in diagram.
Choose file to upload.
Optional enter multipart key.
Finally click send.
We can reproduce your error by selecting binary radio button and uploading file as shown below.

How to call same wsdl from different server

I have a JSF web page.This Web page is calling a .net web service from back end. I want to use this web service on different host(webservice is same just host -wsdllocation- different) but i don't want do recall wsdl from host to my JSF project. Now, im importing wsdl this command line:
$ wsimport -keep -verbose "wsdl url"
and this line have "wsdlurl" but when i upload my webservice to different webserver i must recall webservice with new "wsdlurl" after i must write new code about that. In .net this way very easy if i want to use same wsdl from different host i can just add a wsdl location line to webconfig.xml is there any way in java about this? How can i call same wsdl from different server without writing code?
You can set the URL when creating the service object
By default when you import wsdl files in client and you try to open port to that service, it test the connection to a default url referenced in imported wsdl. For avoid errors first I create Service Object with the URL of the phisical file pointing to wsdl imported.
URL baseUrl = <classname>.class.getClassLoader().getResource("wsdl/Service.wsdl");
ServiceExtended service = new ServiceExtended(baseUrl);
In addition I use an intermediary class that extend from the original ServiceStub class, it's the way that I use for override connection-timeout and request-timeout properties for a future connection, and also you could add some layer of security using Handlers, etc.
public class ServiceExtended extends ServiceOriginal {
...
public ServiceExtended(URL wsdlLocation) {
super(wsdlLocation);
}
public ProductRepository getPersonalizedPort(URL wsdlLocation, int connectTimeout, int requestTimeout) {
ProductRepository port = super.getProductRepositoryPort();
((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, wsdlLocation.toString());
//JAXS-WS for compatibility
((BindingProvider) port).getRequestContext().put("com.sun.xml.ws.connect.timeout", connectTimeout);
((BindingProvider) port).getRequestContext().put("com.sun.xml.ws.request.timeout", requestTimeout);
//JAXS-WS new
((BindingProvider) port).getRequestContext().put("com.sun.xml.internal.ws.connect.timeout", connectTimeout);
((BindingProvider) port).getRequestContext().put("com.sun.xml.internal.ws.request.timeout", requestTimeout);
return port;
}
...
}
this is an example because I save the .wsdl files using maven in "src/main/resources/wsdl/" beside META-INF, feel free to put that files where do you want. Using this way the creation of Service object will doesn't fail because you're pointing to real path, in this case to a file that exists.
After that you cant create the port to a Service pointing to any URL you want.
Would be for example:
url1 = http://server1.com/appname/Service?wsdl
url2 = http://server2.com/appname/Service?wsdl
ServicePort port = service.getPersonalizedPort(url1, 10000, 30000);
o
ServicePort port = service.getPersonalizedPort(url2, 10000, 30000);
This is an example of how you could connect to webservice.
Hope this help.

Adding soap header authentication to wsdl2java generated code

I'm in the process of creating a Java web services client from a wsdl. I used Eclipses's Dynamic Web Project and new Web Services Client to generate the code with wsdl2java with Apache Axis 1.4. I need to add SOAP authentication to this code in order for it to work with the service. I couldn't find a place to do that in the generated code. After copious research I found this, which I've used as the backbone for my code so far.
Adding ws-security to wsdl2java generated classes
Before I was getting a "Error occurred while processing security for the message" or something along those lines. Now I am getting
"Exception: Did not understand "MustUnderstand" header(s):{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security Message: null"
I've tried many things to get past this exception. This is the code I've arrived at now.
javax.xml.namespace.QName headerName = new javax.xml.namespace.QName(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security");
org.apache.axis.message.SOAPHeaderElement header = new org.apache.axis.message.SOAPHeaderElement(headerName);
header.setActor(null);
header.setMustUnderstand(true);
// Add the UsernameToken element to the WS-Security header
javax.xml.soap.SOAPElement utElem = header.addChildElement("UsernameToken");
utElem.setAttribute("Id", "uuid-3453f017-d595-4a5b-bc16-da53e5831cd1-1");
javax.xml.soap.SOAPElement userNameElem = utElem.addChildElement("Username");
userNameElem.setValue("username");
javax.xml.soap.SOAPElement passwordElem = utElem.addChildElement("Password");
passwordElem.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
passwordElem.setValue("password");
header.setProcessed(true);
// Finally, attach the header to the binding.
setHeader(header)
This code is located in my Binding_ServiceStub class (in its' createCall method).
We have created clients in both C# and VB with this wsdl, and there it's as easy as just changing the ClientCredentials variable which is an extension of the proxy class generated. I was hoping for something similar here.
Here's the security policy from the wsdl code as well.
<wsp:Policy><sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"><wsp:Policy><sp:WssUsernameToken10/></wsp:Policy></sp:UsernameToken></wsp:Policy>
Does anyone know what else I can do here? Why this exception is happening? I've tried many different combinations of prefixes and setProcesses and setMustUnderstand values all in vain (and based on my research of this exception).
And if anyone knows a way in which to add Soap header authentication to wsdl2java code I would take that too. Just need this to work and you would think something like this would be a little more straightforward or at least have more examples out there.
Update-
Confirmed that the same header passed using SOAPUI works fine. Must be something with the framework? I created a custom handler to process the SOAP Message but that didn't help. Is Axis 1.4 and JAX-RPC the problem? (I know they're outdated but still...)
Cool. I decided to just use Apache CXF as my framework and using this it's as easy as adding
javax.xml.ws.BindingProvider bp = (javax.xml.ws.BindingProvider) port;
bp.getRequestContext().put("ws-security.username", username);
bp.getRequestContext().put("ws-security.password", password);
Wow that's much better. Don't use Axis 1.4 lesson learned.

How do I specify url in JAX-WS call and avoid initial network connection?

I'm using JAX-WS standard stuff with wsimport http://localhost/Order.wsdl to generate client stub classes.
The live web service is on a different host, so I need to supply a url when I make the service call. My approach so far has been like this (classes below are generated from wsimport):
1. OrderService s = new OrderService (
new URL("https://live/WS/Order"),
new QName(...));
2. OrderServicePort port = s.getOrderServicePort();
3. configureHttpCertificatesStuff(port) // Set up ssl stuff with the port
4. port.placeOrder(args); // The actual ws call
First: Is this the correct way of specifying the url?
Second: It seems the constructor in line 1 actually makes a network call to the new URL! This results in an exception (due to https not being configured), so I never get to the next line.
Background: I am implementing two-way ssl auth as outlined in this question. This means I need to configure ssl stuff in the port before the service call. I can't have the constructor make any connection before I've configured the ssl layer correctly for obvious reasons...
Update:
Apparenty the url is to the WSDL, not the endpoint when using jax-ws standard. This tripped me up. Loading the WSDL directly from file solved that problem.
Setting the endpoint url is done like this:
BindingProvider b = (BindingProvider) port;
b.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointUrl);
One solution would be to have your build process arrange for the WSDL file processed by wsimport to become a class path resource for your app. There are any number of ways to do this, but lets assume you take a JAR-per-service approach. So, you'd run Order.wsdl through wsimport and take the resulting classes, like OrderService and OrderServicePort, and stuff them into order-service.jar. The other thing you could do would be to stuff a copy of Order.wsdl into that same JAR at META-INF/wsdl/Order.wsdl. Assuming that JAR file is then part of the class path for your app, you can get the WSDL's URL by doing:
URL wsdlLocation = Thread.currentThread().getContextClassLoader().getResource("META-INF/wsdl/Order.wsdl");

Categories