Publishing and Consuming Webservices with Grails
We’ve been working on a middle layer application that needs to translate a generic API to a proprietary API which uses our business logic to drive our local hardware. The hardest part has been developing the webservices on an MVC framework. Grails isn’t designed to natively consume or publish web services, but of course, there are plugins and jars available which can add that functionality.
Grails and Xfire
I was assigned to figure out how to do the top level exposing of our methods, while my teammates were working on consuming from the other service. It was easy enough to get to work. The Xfire plugin works quite simply. Just install, add expose = true, and your exposing your webservices. To everyone. With no security. To add security, add a couple of Spring transactional layers (wow, I trivialized, but its not easy at all when you don’t have Spring experience). All was good, every thing was working the way I wanted. I was interacting with Xfire and securing with Acegi.
Then we merged our branches. My stuff broke the consuming part (done with jaxws, not sure how, I didn’t do it).
What on earth? Why won’t this work together?
Xfire is using jaxws too. Oh, but its a different version. And its REALLY old. Development on Xfire stopped before Grails was born. Why the hell is there a plugin that uses such out of date code? Damnit. And there’s no simple upgrade from the Grails Xfire plugin to Apache CXF (it’s replacement). Oh well, throw all this work out and start over.
Grails and CXF
CXF is a completely different beast in terms of what I have to do to get it to work with Grails. No simple “grails install-plugin”. No “add one line to expose”. No good (Grails and CXF) tutorial. Nobody doing what I’m trying to do. (Well, I guess there is someone trying to do it because I find plenty of questions, similar stack traces, and a bunch of hints on where to go, but none of it was assembled together) Hopefully, my experience can help others, and maybe someone who knows a little more about Spring, Grails, or CXF will be able to improve this solution.
Before we get too far, this solution is VERY touchy. It requires you to recompile everytime you make a change to the source. It gives horrible stack traces (but you’re used to that if you’re using grails). Use it at your own risk.
First grab the jars from the CXF Apache site. There are a bunch of jars, and I don’t really know which ones are needed and which ones are not, but I found the following list and it works. Examine WHICH_JARS if you want to be sure it works.
asm-2.2.3.jar commons-lang-2.4.jar commons-logging-1.1.1.jar cxf-2.2.2.jar cxf-manifest.jar FastInfoset-1.2.3.jar geronimo-activation_1.1_spec-1.0.2.jar geronimo-annotation_1.0_spec-1.1.1.jar geronimo-javamail_1.4_spec-1.6.jar geronimo-jaxws_2.1_spec-1.0.jar geronimo-jms_1.1_spec-1.1.1.jar geronimo-servlet_2.5_spec-1.2.jar geronimo-stax-api_1.0_spec-1.0.1.jar geronimo-ws-metadata_2.0_spec-1.1.2.jar jaxb-api-2.1.jar jaxb-impl-2.1.9.jar jaxb-xjc-2.1.9.jar jaxen-1.1.jar jaxws-2_1-mrel2-api.jar jaxws-rt-2.0EA3.jar jetty-6.1.18.jar jetty-util-6.1.18.jar jsr181-api.jar neethi-2.0.4.jar saaj-api-1.3.jar saaj-impl-1.3.2.jar saaj-impl-1.3.jar velocity-1.5.jar wsdl4j-1.6.2.jar wss4j-1.5.7.jar wstx-asl-3.2.8.jar xml-resolver-1.2.jar XmlSchema-1.4.5.jar
Once the jars are in the lib folder, an interface for the exposed class needs to be made in the /src/groovy.
package com.domain.services;
import com.domain.*;
interface Sample{
int methOne(int arg1, String arg2)
}
Then the interface needs to be implemented by a service (well, it doesn’t have to be a service, just the class you want to expose).
import com.domain.*;
class SampleService implements com.domain.services.Sample {
boolean transactional = true
int methOne( int arg1, String arg2){
return 3;
}
Now we have the service all ready to expose, and we just need to tell Spring to do it. We can do this with some xml in the right places. In order to be able to find where I added the XML (so when I need to add another service interface) I created cxf-servlet.xml (don’t try to rename it. It won’t work) in web-app/WEB-INF with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:simple="http://cxf.apache.org/simple"
xmlns:soap="http://cxf.apache.org/bindings/soap"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/bindings/soap
http://cxf.apache.org/schemas/configuration/soap.xsd
http://cxf.apache.org/simple
http://cxf.apache.org/schemas/simple.xsd">
<!--create CXF service-->
<simple:server id="samplePublishedService"
serviceClass="com.domain.services.Sample"
address="/sample">
<simple:serviceBean>
<bean class="SampleService" />
</simple:serviceBean>
</simple:server>
</beans>
This will setup the basic servlet, but doesn’t put it any where. If desired, “address” can be a complete URL, with a different port number, and your app would serve this service there. (i.e. address=”http://localhost:523/services” would allow your SOAP calls over port 523) However, I want to aggregate my services so a user can find the wsdl for any of my services easily.
Next, if you haven’t already, run
grails install-templates
This will create /src/templates/war/web.xml. Add the following to that file (inside the <web-app> tags):
<servlet> <servlet-name>CXFServlet</servlet-name> <display-name>CXF Servlet</display-name> <servlet-class> org.apache.cxf.transport.servlet.CXFServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/ws/*</url-pattern> </servlet-mapping>
Then to /grails-app/conf/spring/resources.xml (or rewrite this to match the .groovy way) add
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:simple="http://cxf.apache.org/simple" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd http://cxf.apache.org/simple http://cxf.apache.org/schemas/simple.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <import resource="classpath:WEB-INF/cxf-servlet.xml" /> </beans>
At this point, you should be able to run. (You probably have to run with -Ddisable.auto.recompile=true. Also “grails clean”ing helps solve some of the errors.) So that is where I will end. I will post soon about Acegi integration.
Point your browser to “http://localhost:8080/testApp/ws/” (for example) to see the list of services. Links will give the wsdls.
Here is a copy of the source.
Tags: Webservices
[...] Publishing and Consuming Webservices with Grails 07 Jul [...]