This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

Bug 165224

Summary: improvement to template for web service clients
Product: webservices Reporter: pbelbin <pbelbin>
Component: CodeAssignee: Milan Kuchtiak <mkuchtiak>
Status: NEW ---    
Severity: blocker    
Priority: P2    
Version: 6.x   
Hardware: All   
OS: All   
Issue Type: ENHANCEMENT Exception Reporter:

Description pbelbin 2009-05-15 00:00:24 UTC
when one drags a web service reference to a java source file, a templated portion of code gets dropped into the source 
file.

currently it does a nice job of using the default constructor to create a service proxy object, and create the binding 
object upon which method calls can be made.

the issue I have with the template as it is, is that by using the default constructor, the client wants to get a 
connection to the live web service that was used to grab the wsdl at development time.

in any environment other than the development environment, this leads to inappropriate attempts (successful or not) to 
that URL and wasted time and effort (and possibly an exception or two).

if the template is updated to do something like the following code, the nasty behavior can be avoided entirely, and 
it's much more clear in the code that the developer needs to do something about identifying just what the run-time URL 
is going to be.

for example:  (YourService and YourPort would be parameterized values for the template to dig out from the web service 
reference that's dragged in to the code).....


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

WebServiceClient ann = YourService.class.getAnnotation(WebServiceClient.class);
QName qname = new QName(ann.targetNamespace(), ann.name());
YourService service = new YourService(new URL("http://test.webservice.com/service/MyService"),qname);
YourPort binding = service.getYourPort();



the above code will 

a) highlight that the coder needs to figure out what the URL should be, and define that in code rather than leaving it 
to the wsdl.
b) not look for the web service that was available during development.
Comment 1 Milan Kuchtiak 2009-05-15 10:37:30 UTC
Thank You. Good idea.
You've meant the runtime location of wsdl file, not the service right ? 
(the service url information is in wsdl)

Another solution would be to use the JAX-WS feature (javax.xml.ws.BindingProvider class):

Sample code snippet:
        
        YourService service = new YourService();
        YourServicePort port = service.getYourServicePort();
         
        // You can change the runtime location of wsdl file by using the following code:
        /*
        javax.xml.ws.BindingProvider binding = (javax.xml.ws.BindingProvider)port;
        binding.getRequestContext().put(
            javax.xml.ws.BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
            "http://test.webservice.com/service/MyService?wsdl");
        */

Would this solution work for you ?

Note: in objects initialized by J2EE container (e.g. in servlets) we already (since Netbeans 6.0) generate
@WebServiceRef annotation for service field:

@WebServiceRef(wsdlLocation = "http://test.webservice.com/service/MyService?wsdl")
YourService service;
Comment 2 pbelbin 2009-05-16 16:38:28 UTC
I'm NOT talking about the location of the WSDL.  I'm talking about where the WSDL is pointing - where the code is 
going to try to connect to.

I think we're on a similar page, but there is a problem with your code....

here is what I'm trying to prevent:

a) developer points the ide to a development server (ie: not localhost) to use a web service by pointing at the WSDL 
using a URL on some other machine.
b) ide stores the wsdl locally along with the URI that points to the development server, and generates proxy code 
(***that has a static copy of the URL pointing to the development server hosting the web service***).
c) the code gets deployed to a production server.
d) what happens when the default constructor is used for the service proxy?  it tries to open a connection with the 
development server.  this is a time-wasting no-no.  the production client should not even attempt this.

ok, so I think we both understand the problem, however:

the code I've provided avoids this from happening because it changes the URL *before* the constructor tries to open a 
connection to the web service.  Your code example does not do this.

in the code you give, we use the default constructor and method to get the service port, and then try to update the 
URI after having obtained the port object:

        YourService service = new YourService();
        YourServicePort port = service.getYourServicePort();

the problem is that after having used the above to lines of code, regardless of what you do afterwards, the client has 
already tried to access the web service that was defined in the WSDL - because the URI is statically built into the 
proxy code.

take a look at the proxy code, and you'll see exactly what I'm talking about.  the default constructor uses the 
statically defined URI that was taken from the WSDL that was used during development.

unfortunately, the designers of the jax-ws api standard didn't think it would be good to provide a constructor variant 
where we could just pass a parameter to alter the way the initial default URI/URL is defined.

what would be great would be a way for the glassfish container to be configured to point to UDDI servers that could 
then dynamically resolve the question of what the URI should be.

however, it seems that UDDI servers have fallen out of favour (possibly due to them being yet another layer of 
abstraction that many don't want to have to deal with).  If UDDI support was more up front in the IDE, this would 
likely change.

does this help?

*that's why my code is the way it is*:  it obtains the QName from the proxy, and creates a new URL, and uses the non-
default constructor for the service.  

in this way, the statically defined default URI for the web service is *never used*.
Comment 3 pbelbin 2009-05-18 17:17:24 UTC
I suppose, a way of achieving a default would be this:

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

WebServiceClient ann = YourService.class.getAnnotation(WebServiceClient.class);
URL url = new URL(ann.wsdlLocation());
QName qname = new QName(ann.targetNamespace(), ann.name());
YourService service = new YourService(url,qname);
YourPort binding = service.getYourPort();

the above gives exactly what you have today - use of the default URL (taken from the WSDL via the annotation), but, 
it's very easy to see where this can be changed to make it use any service URL instead of the one that's been hard 
coded into the proxy.

Comment 4 Milan Kuchtiak 2009-05-19 17:10:14 UTC
Yes, may me the solution found by you is faster. I haven't tested that.

Nevertheless, in Netbeans 6.7 the jax-ws-catalog.xml was implemented (in all project types), and, this means IMHO, the
code below shouldn't try to open the connection with the development server (as you wrote), since all wsdl resources are
mapped to local resources (located under WEB-INF/wsdl) now. 

        YourService service = new YourService();
        YourServicePort port = service.getYourServicePort();

Another point:
We need to distinguish between the URL of wsdl file and URL of the web service :
The 2 lines above are not opening connection with the web service, but with wsdl file.
Only the code related to calling WS operation (on proxy object) is connecting actually to the web service.


As far as jax-ws-catalog.xml, the priorities they are following.
- JAX-WS code is trying to access wsdl file that is mapped to original wsdl url in jax-ws-catalog.xml 
- if not found JAX-WS is looking for originall wsdl location (referenced in service class)
Comment 5 pbelbin 2009-05-26 15:39:08 UTC
ok, if that's true, then, why does the blogger here http://netbeans.dzone.com/articles/portability-and-performance 
think he needs to make the manual changes he suggests?

I see no point in having to make these manual changes.

we should be able to have the default template provide very good code that makes the manual messing around a non-issue 
so that it can be done in code if needed.

please advise.

regards,
peter.
Comment 6 Milan Kuchtiak 2009-05-27 10:09:27 UTC
The manual changes the writer is talking about just depict the functionality of jax-ws-catalog.xml.
They don't need to be done.

Your solution is really interesting and straightforward. We should really 
I am going implement it, but simplify to (example):

=====
QName qname = new QName("http://www.webserviceX.NET/", "CurrencyConvertor");
CurrencyConvertor service = new CurrencyConvertor(new URL("http://www.webservicex.net/CurrencyConvertor.asmx?wsdl"),qname);
CurrencyConvertorSoap port = service.getCurrencyConvertorSoap();
=====

Note: 3 string literals are used there instead of getAnnotation method. IDE should be able to resolve these strings from
generated java source file (YourService.java).
Comment 7 pbelbin 2009-05-28 04:19:15 UTC
Thank you for appreciating that I want to improve things.

There is however, an issue with the solution that you propose.

The issue is that the values that you are expecting the template usage processing to insert into the developer's code 
will become hard coded into the source code.

If (as often happens) something changes in the design of the WSDL for the web service, simply re generating the client 
proxy code will now introduce an update dependancy that could require many pieces of code to have to be visited to 
bring it into alignment.

I am keen to have the solution not become yet another thing that needs to be maintained if such a change happens, as 
there are already enough scenarios that would require manual changes to allow code to work after a change of some sort.

ie: the QName values that you're anticipating...

Using my solution takes the values that were available at the time the proxy code was generated, so it's always going 
to be in sync with the proxy code, and should not have to be revisited due to web service QName variations.

please re-consider using the code I supplied.

best regards,
peter.
Comment 8 Milan Kuchtiak 2009-05-28 08:53:43 UTC
I see your point. On the other hand in case of hard strings the readability of the code is better.

With no hard strings the client code is a little bit more flexible, but after some changes (e.g. when service name is
changed, the client code isn't compilable anyway.

What about the hard coded string used in service constructor :

CurrencyConvertor service = new CurrencyConvertor(new URL("http://www.webservicex.net/CurrencyConvertor.asmx?wsdl"),qname);
vs.
CurrencyConvertor service = new CurrencyConvertor(new URL(ann.wsdlLocation()),qname);

I supposed you wanted to have ability to change it simply in the client code.

If I correctly understand the main goal of this change is avoiding the usage of the URL string created in static code
block, that is contained in generated class, annotated with @WebServiceClient.
The static block will be called in any case (when the class is loaded by class loader). We can only avoid usage of that URL.

I'll consult the stuff with JAX-WS guys. 
The usage of javax.xml.ws.BindingProvider together with jax-ws-catalog.xml looks also flexible enough.
Comment 9 pbelbin 2009-05-28 14:52:05 UTC
I think that your suggestion of

CurrencyConvertor service = new CurrencyConvertor(new URL(ann.wsdlLocation()),qname);

would work well, resulting in something like:

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

WebServiceClient ann = YourService.class.getAnnotation(WebServiceClient.class);
QName qname = new QName(ann.targetNamespace(), ann.name());
YourService service = new YourService(new URL(ann.wsdlLocation()),qname);
YourPort binding = service.getYourPort();
--------------------------------------------------------------------------------

with no hard strings in the generated code.

the thing I am really wanting to avoid is hard coding any strings in the client code, while still making it easy to 
see where to update the URL for the live web service at runtime.

it would be absolutely wonderful if the URL's could actually say something like "uddi:serviceName" and have the jax-ws 
use jax-r to ask the container about the uddi server and find the details itself.

any chance of asking about that when you talk with the jax-ws people?