Saturday, May 31, 2008

Webservice endpoint

The Java JAX-RPC implementation used to create simple to use web service clients, where developer can easily change the web service endpoint as shown below-

MyWsImpl stub = new MyWsImplService_Impl().getXXX();
((Stub) stub)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, "http://myHost/myWs/myWs");

See this entry for details about building JAX-RPC web services.

The JAX-WS has changed the way it generates the clients. As a general practice people keep the WSDL along with their code and create the client side stubs at the build time. The JAX-WS hard codes the WSDL path in the client stub. See the one such generated code below-
package cache;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Logger;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceClient;

@WebServiceClient(name = "MyServiceImplService", targetNamespace = "http://c.b.a/", wsdlLocation = "file:/D:/Test/temp/wsdl/MyServiceImplService.wsdl")
public class MyServiceImplService extends Service {

private final static URL MYSERVICEIMPLSERVICE_WSDL_LOCATION;
private final static Logger logger = Logger
.getLogger(MyServiceImplService.class.getName());

static {
URL url = null;
try {
URL baseUrl;
baseUrl = MyServiceImplService.class.getResource(".");
url = new URL(baseUrl,
"file:/D:/Test/temp/wsdl/MyServiceImplService.wsdl");
} catch (MalformedURLException e) {
logger.warning("Failed to create URL for the wsdl Location: 'file:/D:/Test/temp/wsdl/MyServiceImplService.wsdl', retrying as a local file");
logger.warning(e.getMessage());
}
MYSERVICEIMPLSERVICE_WSDL_LOCATION = url;
}

public MyServiceImplService(URL wsdlLocation, QName serviceName) {
super(wsdlLocation, serviceName);
}

public MyServiceImplService() {
super(MYSERVICEIMPLSERVICE_WSDL_LOCATION, new QName("http://c.b.a/",
"MyServiceImplService"));
}
}
If one tries to create an instance of client using the default constructor then it looks for the WSDL file at the location hard coded in the class and might fail with FileNotFoundException. The workaround for this problem is to call the another consturcutor, passing the endpoint URL and QName. Creating this QName is quite annoying and also that may change in future changes to WSDL. Then what is the easy wayout?

If you look closely at the web service client, it has an annotation @WebServiceClient, which has all the information to create the QName dynamically.
WebServiceClient ann = MyServiceImplService.class
.getAnnotation(WebServiceClient.class);
MyServiceImplService service = new MyServiceImplService(
new URL("ENDPOINT URL OF MY SERVICE"),
new QName(ann.targetNamespace(), ann.name()));

Now it looks pretty simple, is not it?

20 Comments:

Kasi said...

Hello Vinod,

I think there must be a way to ensure that the entry in the .wsdl file is populated correctly
ie
the entry
soap:address location="REPLACE_WITH_ACTUAL_URL"
is the culprit .

the "REPLACE_WITH_ACTUAL_URL" should be replaced dynamically at runtime - as the the wsdl gets rebuilt everytime, it becomes tedious to do a cut and paste of this entry .

Any thoughts please , much appreciated

Anonymous said...

Kasi,

'REPLACE_WITH_ACTUAL_URL' is replaced at runtime, you can test it by viewing WSDL (using service URL) of a deployed application in browser. If WSDL is bundled with client application then it has to be changed programmatically while invoking service methods.

Anonymous said...

thats, cool. Thx

Anonymous said...

I am attempting a JAX WS Client w/ a local wsdl. The web service client class looks very similar to your example. When I define the URL w/ the local path of the wsdl I receive the error - "main" java.lang.IllegalArgumentException: getNamespaceURI(String prefix) is called with a null prefix. When I put the Endpoint URL in instead, I get the error - com.sun.xml.ws.wsdl.parser.InaccessibleWSDLException: 2 counts of InaccessibleWSDLException. One last thing, the code was generated using the web service client builder in MyEclipse 6.6 . Any help would be greatly appreciated.

Anonymous said...

Not sure about first error, but second one clearly mentions that it was not able to access the WSDL.

If you can post the code and versions of the JAX-WS other technologies being used, that would help in finding a solution.

Anonymous said...

I am using jax_ws 2.1. I have tested the web service as well w/ soap UI so I know that it is functioning. Here is the source code.

package com.aaa.Cybersource;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;

/**
* This class was generated by the JAX-WS RI. JAX-WS RI 2.1.3-hudson-390-
* Generated source version: 2.0
*
* An example of how this class may be used:
*
*
* ElectronicPaymentService service = new ElectronicPaymentService();
* ElectronicPaymentPortType portType = service.getElectronicPaymentPort();
* portType.runCreditCardTransaction(...);
*
*
*
*
*/
@WebServiceClient(name = "ElectronicPaymentService",
targetNamespace = "http://XXXXXXXXXXX.com/electronicpayment/2008/08/15",
wsdlLocation = "file:/C:/XXXXXXXX/epayment2/ePayment/wsdl/electronicpayment/ElectronicPaymentServiceImplementationCC.wsdl")


public class ElectronicPaymentService extends Service {

private final static URL ELECTRONICPAYMENTSERVICE_WSDL_LOCATION;
private final static Logger logger = Logger
.getLogger(com.XXX.Cybersource.ElectronicPaymentService.class
.getName());



static {

System.out.println("ElectronicPaymentService begin 2:");

URL url = null;
try {
URL baseUrl;
baseUrl = com.XXX.Cybersource.ElectronicPaymentService.class
.getResource(".");
url = new URL(
baseUrl, "https://111.11.111.111:1111/ElectronicPaymentService.asmx");
// "file:/C:/XXXXXX/epayment2/ePayment/wsdl/electronicpayment/ElectronicPaymentServiceImplementationCC.wsdl");
} catch (MalformedURLException e) {
logger
.warning("Failed to create URL for the wsdl Location: 'file:/C:/XXXXX/epayment2/ePayment/wsdl/electronicpayment/ElectronicPaymentServiceImplementationCC.wsdl', retrying as a local file");
logger.warning(e.getMessage());
}


// URL url = null;
// ClassLoader classloader = Thread.currentThread().getContextClassLoader();
// url = classloader.getResource("WEB-INF/wsdl/electronicpayment/ElectronicPaymentServiceImplementationCC.wsdl");



ELECTRONICPAYMENTSERVICE_WSDL_LOCATION = url;

System.out.println("ElectronicPaymentService url: "+url.toString());
}

public ElectronicPaymentService(URL wsdlLocation, QName serviceName) {

super(wsdlLocation, serviceName);
}

public ElectronicPaymentService() {
super(ELECTRONICPAYMENTSERVICE_WSDL_LOCATION, new QName(
"http://XXXXXXXXXXX.com/electronicpayment/2008/08/15",
"ElectronicPaymentService"));
}

/**
*
* @return returns ElectronicPaymentPortType
*/
@WebEndpoint(name = "ElectronicPaymentPort")
public ElectronicPaymentPortType getElectronicPaymentPort() {
return super.getPort(new QName(
"http://XXXXXXXXXXX.com/electronicpayment/2008/08/15",
"ElectronicPaymentPort"), ElectronicPaymentPortType.class);
}



}

Anonymous said...

My problem is solved for the naccessibleWSDLException. As it turned out the port in the endpoint was incorrect. Once that was corrected I got it working.

cancelledout said...

QUOTE:
Kasi,

'REPLACE_WITH_ACTUAL_URL' is replaced at runtime, you can test it by viewing WSDL (using service URL) of a deployed application in browser. If WSDL is bundled with client application then it has to be changed programmatically while invoking service methods.

What do you mean by programmatically? Meaning writing onto the xml .wsclient file?

Anonymous said...

@cancelledout,

Programmatically means something like-

ServiceImplService service = new MyServiceImplService(new URL("ENDPOINT URL OF MY SERVICE"), new QName(ann.targetNamespace(), ann.name()));

Ranjan said...

hello Vinod,


I have a web service deployed on our own web server (not on any IIS or Tomcat)
. So, all I have is a WSDL file and a webserver.exe (that has the web
services implemented inside).

Using this WSDL file, I have already developed a client application in .net
(C#) which is running properly.

Now I have to create another client application in JAVA using the same WSDL.
But I dont know how to proceed (I am new to JAVA). To be specific I have some
questions to proceed for it.. Please see the code snippet I wrote in Java.

trustAllHttpsCertificates();
HttpsURLConnection.setDefaultHostnameVerifier(hv);

QName serviceName = new QName("Sphericall");
URL url = new URL("https","localhost",8085,"/SphericallService");

testthebest.Sphericall_Service service = new testthebest.Sphericall_Service
(url, serviceName);
//service.create(serviceName);

testthebest.Sphericall port = service.getSphericall();
// TODO initialize WS operation arguments here
testthebest.StartSessionRequest value = new testthebest.StartSessionRequest()
;

In the above code segment, I have written code to bypass certificate check,
then for hostname verification and finally have tried to create the object of
service.

But while object creation I receive following exceptions. (WSDL not found).
Kindly note that in code I have assigned the new URL for service. And in
project I have attached the web reference (WSDL) which is kept in my
machine's D drive. So, Its was taking the URL as D:/xyz.wsdl but as its not
the correct path of URL so I changed it to "https://localhost:
8085/SphericallService". But its not running ..

com.sun.xml.ws.wsdl.parser.InaccessibleWSDLException: 2 counts of
InaccessibleWSDLException.

java.io.FileNotFoundException: https://localhost:8085/SphericallService
java.io.FileNotFoundException: https://localhost:8085/SphericallService?wsdl

As I said, My service is on our own developed webserver and it runs like a
executable. And the wsdl file is sepeartely provided. So, there is no path
exists like "https://localhost:8085/SphericallService?wsdl" for wsdl.

Kindly suggest if you have any idea on it.

regards,

Anonymous said...

@Ranjan,

What URL you used while accessing the web service using C# client? Can you see the WSDL in browser using that URL? If yes, that URL should work for Java client as well.

Ranjan said...

Please see the client code for creating service object.
public class SphericallFactory
{

static public Sphericall Create(string username,string password)
{

ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CertValidationCallback);

Sphericall service = new Sphericall();

service.Credentials = new System.Net.NetworkCredential(username,password);
service.PreAuthenticate = true;
return service;
}

// Change the address of the server
static public void SetServerAddress(Sphericall service, string address)
{
if (address != null && address != "")
{
string url = "";
if (address.IndexOf(':') >= 0)
{
// Check if the last digit is even
char digit = address[address.Length - 1];
string even = "02468";
if (even.IndexOf(digit) >= 0)
url = "http://" + address + "/SphericallService";
else
url = "https://" + address + "/SphericallService";
}
else
url = "https://" + address + ":8081/SphericallService";
service.Url = url;
}
}

// Used to unconditionally validate a certificate
static public bool CertValidationCallback(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
}

So, we use the same path i.e.
https://localhost:8085/SphericallService

when I run this in browser as "https://localhost:8085/sphericallservice?wsdl", I don't see the wsdl but error

-
-
-
SOAP-ENV:Client
HTTP Error: 404 Not Found





But in .net the client is running well.

Ranjan said...

I dont why my last post is not visible. In that post I provided the .net code snippet.
To be specific I am using the same URL (used in .net) in Java i.e. https://loaclhost:8085/SphericallService but when I run this URL in borwser (with ?wsdl), I get 404 not found error. But the client application runs smoothly. There is no issue.
Please help.

Anonymous said...

@Ranjan,

So the exception you are facing is the real one (404 - not found). This web service can't be consumed by the client (Java in this case) as it is not accessible over HTTP(S).

Looks like this web service is published using some proprietary technology, which is not cross platform.

Ranjan said...

So, I understand two things (from your replies). Kindly validate.

1. Java needs wsdl (generated proxy classes) at two levels, one at compile time (which is available in my case) and one at run time (which is missing in my case).
If yes, then can't we bypass it using some method in java? Further why this .net (C#) doesn't require this wsdl at run time?

2. Is there any method to prove it that our web service uses some proprietary technology which is not cross platform?

Regards,

Ranjan said...

Hi Vinod,

Would you please verify above statements?

Thanks again.

Anonymous said...

@Ranjan,

#1 - Your understanding about Java is correct here. I am not sure about C#.
#2 - Not reachable (404) WSDL is the sufficient proof, I believe.

Ranjan said...

Hi Vinod,

Finally, I am able to connect to my service. Below links helped a lot.

http://stackoverflow.com/questions/764772/jax-ws-loading-wsdl-from-jar

http://stackoverflow.com/questions/4163586/jax-ws-client-whats-the-correct-path-to-access-the-local-wsdl

I have made it work on netbeans & Apache CXF.
Still facing the problem on Eclipse.

Regards,
Rakesh

Mariusz said...

Hi, Vinod!

Thanks for this tip. It helps me a lot today! :)

Have a nice day!

Ganesh said...

I try to test the sample SOAP message from SOAP UI.

I am getting the following error
wsse:FailedCheck
Security Data : UsernameToken authentication failed.

http://www.cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf

Page:12

This is sample code – Not sure why it not working from SOAP UI

I placed same merchant ID and the transaction key generated for SOAP UI in test Cybersource business center. I have verified that I have copied the content of key correctly.

not able to move further.