Download NetBeans!

20071103 Saturday November 03, 2007

Groovy Web Service

Long cherished dream of mine, reverberating through the darker corners of my innermost thoughts... figuring out how to consume a web service in Groovy. "A web service? In Groovy? That must mean you use the same standard Java libraries for JAX-WS, or JAX-RPC, generate client stubs and then use them to connect to the web service, right?" Wrong. Forget stubs. Groovy provides its own library for web services. Just to simplify the life of developers, since it is incredibly lightweight and gets the job done painlessly. And there are no stubs.

Everything, though slightly out of date, is described here:

Groovy Web Services

I tried the final example, with success. Here's my Groovy script:

import groovyx.net.ws.WSClient

class TryIt {
    
    groovy.swing.SwingBuilder swing = new groovy.swing.SwingBuilder()
    
    def proxy = new WSClient("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL", TryIt.class.classLoader)
    def currency = ['USD', 'EUR', 'CAD', 'GBP', 'AUD', 'SGD']
    def rate = 0.0

    void main() {

        def refresh = swing.action(
          name:'Refresh',
          closure:this.&refreshText,
          mnemonic:'R'
        )

        def frame = swing.frame(title:'Currency Demo') {
          panel {
            label 'Currency rate from '
            comboBox(id:'from', items:currency)
            label ' to '
            comboBox(id:'to', items:currency)
            label ' is '
            textField(id:'currency', columns:10, rate.toString())
            button(text:'Go !', action:refresh)
          }
        }
        frame.pack()
        frame.show()
        
    }
    
    def refreshText(event) {
          rate = proxy.ConversionRate(swing.from.getSelectedItem(), swing.to.getSelectedItem())
          swing.currency.text = rate
    }
    
}

It's incredible that this is literally all the code that you need. Nothing more in any shape or form. No configuration files, no stubs, no XML, no anything else. The above is almost the same as in the original document referred to above, but slightly tweaked (e.g., the 'def' keyword had been omitted in a few places). When I call the above class from a Java class (since NetBeans IDE doesn't support the running of Groovy classes, just Groovy scripts), the following Swing form appears, created from the SwingBuilder code above:

Then I enter a number (in the above case, I typed '500.00') and press Refresh. A number that doesn't make much sense to me returns, but that's how things go with web services over which you have no control. They're just black boxes, spewing something back to you upon request:

To set this up in NetBeans IDE, apart from creating the Groovy class in a Groovy file and calling it from a Java class, you need to be aware of the following:

  • Make sure to include the groovyws JAR when you compile, otherwise compilation will fail, since you're using Groovy's WSClient class:

    <target name="groovyc" description="groovyc">
        <taskdef name="groovyc" 
                 classpath="lib/groovy-all-1.1-rc-2-SNAPSHOT.jar" 
                 classname="org.codehaus.groovy.ant.Groovyc"/>
        <groovyc srcdir="${src.dir}" destdir="groovy">
            <classpath path="lib/groovy-all-1.1-rc-2-SNAPSHOT.jar"/>
            <classpath path="lib/groovyws-all-0.1.jar"/>
        </groovyc>
    </target>

    I can't remember where I got that JAR from. I googled a lot and found it referred to somewhere, after groovyws-standalone.jar turned out to not include everything I needed. (But maybe I'm wrong, I'll check this.) Now that you know it is called groovyws-all-0.1.jar, you should be able to find it. (Would be cool if it were bundled with the standard Groovy distribution.)

  • If you're using JDK 6, you need to set the endorsed dir, in the Run panel, in the Project Properties dialog box:

    -Djava.endorsed.dirs=copy the value of jaxws.endorsed.dir from nbproject/private.properties

  • You need ant-1.7.0.jar and the Groovy 1.1 JAR (or some other version of the Groovy 1.1 JAR) in your application's Libraries node.

And, I think, that's it. One thing thing I'm going to try is to get the Daily Dilbert web service to return comics via Groovy, as above, although I suspect I may end up in trouble with the images. But, before beginning that, I did a bit of tweaking, and not much later I now have the world's simplest web service client:

import groovyx.net.ws.WSClient

class TryIt {
    
    groovy.swing.SwingBuilder swing = new groovy.swing.SwingBuilder()
    
    def proxy = new WSClient("http://saintbook.org/MightyMaxims/MightyMaxims.asmx?WSDL", TryIt.class.classLoader)
        
    void main() {

        def frame = swing.frame(title:'Thought for the Day') {
          panel {
            label(proxy.ForToday())
          }
        }
        
        frame.pack()
        frame.show()
        
    }
        
}

The above (which is NOT a snippet, it is the entire web service client), results in this when I run it:

And here's one that's a bit more interactive, similar to the first one, but this time sending snippets of Shakespeare to a web service in order to retrieve full speeches, which has been referred to several times before in this blog:

import groovyx.net.ws.WSClient
import java.awt.BorderLayout

class TryIt {
    
    groovy.swing.SwingBuilder swing = new groovy.swing.SwingBuilder()
    
    def proxy = new WSClient("http://www.xmlme.com/WSShakespeare.asmx?WSDL", TryIt.class.classLoader)
        
    void main() {

        def frame = swing.frame(title:'Shakespeare',size:[300,300]) {
          panel(layout: new BorderLayout()) {
            textField(id:'quote',constraints: BorderLayout.CENTER, "fair is foul")
            textArea (id:'area',constraints: BorderLayout.NORTH, proxy.GetSpeech(swing.quote.text).replaceAll("><",">\n   <"))
            button(constraints: BorderLayout.SOUTH,"Search",action:refresh)
          }
        }
        
        frame.pack()
        frame.show()
        
    }
    
    def refresh = swing.action(
          name:'Refresh',
          closure:this.&refreshText,
          mnemonic:'R'
    )
    
    def refreshText(event) {
          def newQuote = proxy.GetSpeech(swing.quote.text)
          swing.area.text = newQuote.replaceAll("><",">\n   <")
    }
        
}

Clearly, it's all really cool and lightweight. I can imagine it can be very useful for doing quick tests as part of a larger process. But the above would make sense in a production environment too, I reckon. Why consume web services the hard way if you can do it the easy way? For all details on this, see the aforementioned page, which seems to be the only one that describes this cool Groovy feature.

In other news. Put 13949712720901ForOSX in your blog to let Apple know that you want Java 6 support in Mac OS, as described here! And then go here to see all the other people who have already done so...

Nov 03 2007, 12:41:45 PM PDT Permalink

Trackback URL: http://blogs.sun.com/geertjan/entry/groovy_web_service
Comments:

Nice post. Remember to register your RSS Feed on Groovy Blogs (http://www.groovyblogs.org), this way it will be available for the Groovy community.

Posted by Antonio Goncalves on November 04, 2007 at 05:14 AM PST #

Done!

Posted by Geertjan on November 04, 2007 at 05:19 AM PST #

Have you tried this with the terra service?
http://terraservice.net/TerraService.asmx?WSDL

I get an error as soon as I define the proxy with that WSDL;

INFO: Created classes: com.terraserver_usa.terraserver.AreaBoundingBox, com.terraserver_usa.terraserver.AreaCoordinate,
...
com.terraserver_usa.terraserver.UtmPt

org.apache.cxf.service.factory.ServiceConstructionException
at org.apache.cxf.endpoint.dynamic.TypeClassInitializer.begin(TypeClassInitializer.java:94)
at org.apache.cxf.service.ServiceModelVisitor.visitOperation(ServiceModelVisitor.java:74)
at org.apache.cxf.service.ServiceModelVisitor.visitOperation(ServiceModelVisitor.java:95)
at org.apache.cxf.service.ServiceModelVisitor.walk(ServiceModelVisitor.java:48)
at org.apache.cxf.endpoint.dynamic.DynamicClientFactory.createClient(DynamicClientFactory.java:250)
at org.apache.cxf.endpoint.dynamic.DynamicClientFactory.createClient(DynamicClientFactory.java:138)
at groovyx.net.ws.WSClient.<init>(WSClient.java:96)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
at org.codehaus.groovy.runtime.MetaClassHelper.doConstructorInvoke(MetaClassHelper.java:562)
at groovy.lang.MetaClassImpl.doConstructorInvoke(MetaClassImpl.java:1756)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:758)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:688)
at org.codehaus.groovy.runtime.Invoker.invokeConstructorOf(Invoker.java:163)
at org.codehaus.groovy.runtime.InvokerHelper.invokeConstructorOf(InvokerHelper.java:140)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeNewN(ScriptBytecodeAdapter.java:243)
at wsterra.main(wsterra.groovy:7)
Caused by: java.lang.ClassNotFoundException: byte[]
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)

Posted by sean d on November 07, 2007 at 09:46 AM PST #

I tried to connect to local Jira, but no luck... I got exception with creating proxy:
java.lang.ClassCastException: org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
at javax.xml.parsers.DocumentBuilderFactory.newInstance(DocumentBuilderFactory.java:98)
at java.util.XMLUtils.getLoadingDoc(XMLUtils.java:75)
at java.util.XMLUtils.load(XMLUtils.java:57)
at java.util.Properties.loadFromXML(Properties.java:701)
at org.apache.cxf.common.util.PropertiesLoaderUtils.loadAllProperties(PropertiesLoaderUtils.java:71)
at org.apache.cxf.wsdl11.WSDLManagerImpl.registerInitialExtensions(WSDLManagerImpl.java:209)
at org.apache.cxf.wsdl11.WSDLManagerImpl.<init>(WSDLManagerImpl.java:97)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)

Posted by mare on November 12, 2007 at 01:05 PM PST #

I found out I'm not the only one:

http://www.nabble.com/groovy-grails%2C-CXF-and-generated-classes-tf4700854.html#a13523267

Posted by mare on November 12, 2007 at 01:16 PM PST #

I put some examples of using GroovyWS with Terra on GrrovyWS web sites

Posted by tog on November 25, 2007 at 09:15 AM PST #

I am very interested in GroovyWS and your blog is very helpful to me, but I can't find groovyws-all-0.1 jar in the web.
Could you help me to locate it?

Best regards,
Gacgde

Posted by gacgde on November 30, 2007 at 06:25 AM PST #

Just go here and download it there, it has a different name now, but does the same:
http://groovy.codehaus.org/GroovyWS

Posted by Geertjan on November 30, 2007 at 06:28 AM PST #

Thank you for the link.
But I get this error message when connecting to a service somebody has designed in my company:
Unable to create JAXBContext for generated packages: "generated" doesn’t contain ObjectFactory.class or jaxb.index.
Before this error message, in the list of generated classes, the right name DataModel.ObjectFactory appears. Then why does JAXB 2.1 looks for a "generated" class? Any idea?
I have tested with Java 1.5 or 1.6, Groovy 1.1.rc2 or rc3.
Thanks,
Gacgde

Posted by JAXB 2.1 issue? on November 30, 2007 at 12:34 PM PST #

Geertjan,
I'm getting the same problem :

AXBContext for generated packages: "generated" doesn’t contain ObjectFactory.class or jaxb.index.

Did you find a solution ?
thanks,
Tom

Posted by Tom Duerr on January 29, 2008 at 07:38 AM PST #

I tried using GroovyWS in linux and it works
perfect, but when i try to use it with Windows,
I get the following error:

[ERROR] IOException during exec() of compiler "javac". Check your path environment variable.

My JAVA_HOME and GROOVY_HOME are both set and
JAVA_HOME\bin is in my path.

Any suggestions?

Posted by Schwame on March 28, 2008 at 09:20 AM PDT #

Suggestion: stop using Windows. :-)

Posted by Geertjan on March 28, 2008 at 09:26 AM PDT #

I tried to get the BookService (from the Groovy site) to work. I'm getting the following exception in client code:
Exception thrown: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'Groovy in Action' with class 'java.lang.String' to class 'javax.xml.bind.JAXBElement'

Using Groovy 1.5.5 and groovyws-standalone-0.3.1.jar

I can get simple WS to work fine.

This is occuring when the code is trying to set the title of the book.

This seems to be a problem with using the Book class. I removed the "defaultnamespace" and got a Book object that worked until the addBook method was called. It would fail silently and never call the service.

All of the code is in the same dir on the same machine.

Posted by Dale Frye on April 25, 2008 at 02:01 PM PDT #

Hi Geertjan,

do you have any experience with webservice authentication? I try to get a webservice running with authentication but I get alway this error:

No such property: user for class: groovyx.net.ws.WSClient

WSClient client = new WSClient("http://localhost:8080/imap/services/ReportService?wsdl", this.class.classLoader)
client.user="user"
client.password="password"

I don´t find a running example for this on the web

Thx

Alwin

Posted by Alwin on May 07, 2008 at 01:31 AM PDT #

Hi Tom,

i had the same problem trying the example using windows. The solution can be found reading the error message: "Check your path environment variable."

After adding the jdk/bin directory to the system path - all works great. Could it be that your JAVA_HOME points to a JRE instead of a JDK?

Posted by dirksan on June 05, 2008 at 12:30 PM PDT #

Post a Comment:

Name:
E-Mail:
URL:

Your Comment:

HTML Syntax: NOT allowed