Search

Dark theme | Light theme
Showing posts with label SOAP web services. Show all posts
Showing posts with label SOAP web services. Show all posts

November 15, 2010

Groovy Goodness: Use GroovyWS to Access SOAP Web Services (Part 3)

We learned in the previous posts (Part 1, Part 2) how to create a Grails SOAP web service and how to access it with GroovyWS. In this post we learn how we can apply logging interceptors to our client. The output of these logging interceptors is the XML send and received from the SOAP web service. This can be very useful for tracing the messages or for debugging.

The WSClient class of GroovyWS has a property named client of type org.apache.cxf.endpoint.Client. This is our entry point to the Apache CXF client support. We need a way to get a reference to the client property so we can apply logging interceptors for the input and output. Unfortunately the WSClient API has not a method to get a reference to the property. But we write out client code in Groovy so we can use Groovy's MetaClass support to write out own get method to return the client property.

package com.mrhaki.groovyws.client

import groovyx.net.ws.WSClient
import org.apache.cxf.interceptor.LoggingInInterceptor
import org.apache.cxf.interceptor.LoggingOutInterceptor

class BlogWSClient {

    String wsdlUrl

    def proxy

    def findAuthor(String name) {
        createProxy()
        def searchRequest = createSearchRequest(name)
        proxy.findAuthor searchRequest
    }

    private def createSearchRequest(String name) {
        def searchRequest = proxy.create('com.mrhaki.groovyws.server.SearchRequest')
        searchRequest.authorName = name
        searchRequest
    }

    private void createProxy() {
        if (!proxy) {
            WSClient.metaClass.getCxfClient = { ->
                delegate.client
            }
            proxy = new WSClient(wsdlUrl, this.class.classLoader)
            proxy.initialize()

            def cxfClient = proxy.cxfClient
            cxfClient.inInterceptors.add(new LoggingInInterceptor())
            cxfClient.outInterceptors.add(new LoggingOutInterceptor())
        }
    }

}

After this change we can run our tests again from the Gradle build file with $ gradle -q test. We can open the generated test result HTML file and look for the System.err output to see the input and output messages:

Nov 14, 2010 11:00:15 PM org.apache.cxf.interceptor.LoggingOutInterceptor$LoggingCallback onClose
INFO: Outbound Message
---------------------------
ID: 1
Address: http://localhost:8080/server/services/blog
Encoding: UTF-8
Content-Type: text/xml
Headers: {SOAPAction=[""], Accept=[*/*]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns1:findAuthor xmlns:ns1="http://server.groovyws.mrhaki.com"><in0 xmlns="http://server.groovyws.mrhaki.com"><authorName>mrhaki</authorName></in0></ns1:findAuthor></soap:Body></soap:Envelope>
--------------------------------------
Nov 14, 2010 11:00:15 PM org.apache.cxf.interceptor.LoggingInInterceptor logging
INFO: Inbound Message
----------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml;charset=UTF-8
Headers: {content-type=[text/xml;charset=UTF-8], Date=[Sun, 14 Nov 2010 22:00:15 GMT], transfer-encoding=[chunked], Server=[Apache-Coyote/1.1]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soap:Body><ns1:findAuthorResponse xmlns:ns1="http://server.groovyws.mrhaki.com"><ns1:out><blogItems xmlns="http://server.groovyws.mrhaki.com"><BlogItem><id>2</id><text>Sample blogitem one.</text><title>Title1</title><version>0</version></BlogItem><BlogItem><id>1</id><text>Sample blogitem two.</text><title>Title2</title><version>0</version></BlogItem></blogItems><id xmlns="http://server.groovyws.mrhaki.com">1</id><name xmlns="http://server.groovyws.mrhaki.com">mrhaki</name><version xmlns="http://server.groovyws.mrhaki.com">0</version></ns1:out></ns1:findAuthorResponse></soap:Body></soap:Envelope>
--------------------------------------

November 14, 2010

Groovy Goodness: Use GroovyWS to Access SOAP Web Services (Part 2)

In a previous post we learned how to create a SOAP web service with the XFire plugin in Grails. The generated WSDL defined that the properties of our objects could be null by default and the minimum occurence is 0. And because of this we must work with JAXBElement objects to get the real object values. In this post we change the property mapping so the properties cannot be null and have a minimum occurence of 1. And then we can change our client code and deal with the object values directly instead of via a JAXBElement object.

Grails SOAP web service

We can change the default mapping of our objects that is generated by the XFire plugin. We must add an Aegis mapping XML file to our classpath and in this file we can define the custom mapping we want to apply. The name of the file is <ClassName>.aegis.xml and must be in the same package as the class we are writing the custom mapping for. If we add the XML files to the src/java directory of our Grails application then the files will be copied to the classpath automatically.

After we have created the XML mapping files we must add an extra runtime dependency. So we change the grails-app/conf/BuildConfig.groovy file and add a dependency to Jaxen, because this is needed to make the custom mapping work.

<!-- File: src/java/com/mrhaki/groovyws/server/Author.aegis.xml -->
<mappings>
    <mapping>
        <property name="name" minOccurs="1" nillable="false"/>
        <property name="blogItems" minOccurs="1" componentType="com.mrhaki.groovyws.server.BlogItem"/>
    </mapping>
</mappings>
<!-- File: src/java/com/mrhaki/groovyws/server/BlogItem.aegis.xml -->
<mappings>
    <mapping>
        <property name="title" minOccurs="1" nillable="false"/>
        <property name="text" minOccurs="1" nillable="false"/>
    </mapping>
</mappings>
<!-- File: src/java/com/mrhaki/groovyws/server/SearchRequest.aegis.xml -->
<mappings>
    <mapping>
        <property name="authorName" minOccurs="1" nillable="false"/>
    </mapping>
</mappings>
// File: grails-app/conf/BuildConfig.groovy
grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
grails.project.dependency.resolution = {
    inherits("global") {
    }
    log "warn"
    repositories {
        grailsPlugins()
        grailsHome()
        grailsCentral()
        mavenCentral()
    }
    dependencies {
        runtime('jaxen:jaxen:1.1.1') {
            transitive = false
        }
    }
}

We can run our Grails application with $ grails run-app and open the URL http://localhost:8080/server/services/blog?wsdl to see the changes in the generated WSDL file.

GroovyWS Client

Because the definition of our SOAP web service is changed we can also change the client code. We now no longer have use JAXBElement objects, so our code is much cleaner. We can access the object types directly. For example for the dynamic SearchRequest object we can set the authorName property directly. In our old client code we had to create a JAXBElement object to set the value.

package com.mrhaki.groovyws.client

import groovyx.net.ws.WSClient

class BlogWSClient {

    String wsdlUrl

    def proxy

    def findAuthor(String name) {
        createProxy()
        def searchRequest = createSearchRequest(name)
        proxy.findAuthor searchRequest
    }

    private def createSearchRequest(String name) {
        def searchRequest = proxy.create('com.mrhaki.groovyws.server.SearchRequest')
        searchRequest.authorName = name
        searchRequest
    }

    private void createProxy() {
        if (!proxy) {
            proxy = new WSClient(wsdlUrl, this.class.classLoader)
            proxy.initialize()
        }
    }

}
package com.mrhaki.groovyws.client

import spock.lang.Specification

class BlogWSClientSpec extends Specification {

    def client = new BlogWSClient(wsdlUrl: 'http://localhost:8080/server/services/blog?wsdl')

    def "get author with name mrhaki and two blog items"() {
        when:
        def author = client.findAuthor('mrhaki')

        then:
        'mrhaki' == author.name
        def arrayOfBlogItems = author.blogItems
        def blogItems = arrayOfBlogItems.blogItem
        2 == blogItems.size()
        'Title1' == blogItems[0].title
        'Title2' == blogItems[1].title
        'Sample blogitem one.' == blogItems[0].text
        'Sample blogitem two.' == blogItems[1].text
    }

}

In the next post we see how we can add logging interceptors to our client so we can see the input and output from the invocation of the SOAP web service.

November 11, 2010

Groovy Goodness: Use GroovyWS to Access SOAP Web Services

With the GroovyWS module we can consume SOAP web services. Under the hood GroovyWS uses CXF to dynamically create all required classes from a WSDL file. These classes are available via the classloader in our code. So we don't need to convert a WSDL file first to source files, but the classes are generated dynamically and can be used directly in our code. This means that if we can access the WSDL for a web service we can invoke the web service without any explicit code generation.

In this blog post we first write a SOAP web service with Grails and the XFire plugin. Next we create a Gradle project with the client code to invoke and use the SOAP web service we just created. We use Spock to write a specification where we really invoke the web service client and check the results. That is a lot of Groovyness in our project!

Grails SOAP webservice

We start by creating a new Grails project and install the XFire plugin. Then we create a new Grails service, BlogService, and use the plugin to expose the service as SOAP webservice. We create one method in our service: Author findAuthor(SearchRequest search). The parameter search is of type SearchRequest and contains the property authorName, which is used to find an Author object. We define this type to show how we can dynamically create an instance with GroovyWS in the client code.

We also create two domain classes: Author and BlogItem. An Author has a one-to-many relation with BlogItem. Finally we write code in the BootStrap to create a single author with two blog items.

$ grails create-app server
$ cd server
$ grails install-plugin xfire
$ grails create-service com.mrhaki.groovyws.server.Blog
$ grails create-domain-class com.mrhaki.groovyws.server.Author
$ grails create-domain-class com.mrhaki.groovyws.server.BlogItem
// File: grails-app/services/com/mrhaki/groovyws/server/BlogService.groovy
package com.mrhaki.groovyws.server

class BlogService {

    // Make this service a SOAP web service.
    static expose = ['xfire']

    static transactional = true

    Author findAuthor(SearchRequest search) {
        Author.findByName(search.authorName)
    }

}
// File: grails-app/domain/com/mrhaki/groovyws/server/Author.groovy
package com.mrhaki.groovyws.server

class Author {

    String name

    static hasMany = [blogItems: BlogItem]

    static mapping = {
        blogItems lazy: false, sort: 'title'
    }

}
// File: grails-app/domain/com/mrhaki/groovyws/server/BlogItem.groovy
package com.mrhaki.groovyws.server

class BlogItem {

    String text
    String title

    static belongsTo = [author: Author]

    // We must use xmlTransients so belongsTo doesn't
    // cause a StackOverflowError by the XFire plugin.
    static xmlTransients = ['author']

}
// File: src/groovy/com/mrhaki/groovyws/server/SearchRequest.groovy
package com.mrhaki.groovyws.server

class SearchRequest {
    String authorName
}
// File: grails-app/conf/BootStrap.groovy
import com.mrhaki.groovyws.server.*

class BootStrap {

    def init = { servletContext ->
        def blogItem1 = new BlogItem(title: 'Title1', text: 'Sample blogitem one.')
        def blogItem2 = new BlogItem(title: 'Title2', text: 'Sample blogitem two.')
        def author = new Author(name: 'mrhaki')
        author.addToBlogItems(blogItem1)
        author.addToBlogItems(blogItem2)
        author.save()
    }

    def destroy = {
    }
}

We are ready to start our Grails application and we open the url http://localhost:8080/server/services/blog?wsdl to see the generated WSDL file. If we see the WSDL contents we know everything works.

GroovyWS client

Okay we are halfway. Now it is time to write the client code to access our Grails webservice. We create a Gradle project build file with the required dependencies. Next we create the file BlogWSClient.groovy. In this file we use GroovyWS to create the client code. And finally we create a Spock specification to invoke BlogWSClient: BlogWSClientSpec.groovy.

// File: build.gradle
apply plugin:'groovy'

repositories {
    mavenCentral()
}

dependencies {
    groovy 'org.codehaus.groovy:groovy-all:1.7.5'
    compile 'org.codehaus.groovy.modules:groovyws:0.5.2'  // GroovyWS dependency.
    testCompile 'org.spockframework:spock-core:0.4-groovy-1.7'
}
// File: src/main/groovy/com/mrhaki/groovyws/client/BlogWSClient.groovy
package com.mrhaki.groovyws.client

import groovyx.net.ws.WSClient
import javax.xml.bind.JAXBElement
import javax.xml.namespace.QName

class BlogWSClient {

    String wsdlUrl

    def proxy

    def findAuthor(String name) {
        createProxy()
        def searchRequest = createSearchRequest(name)
        invokeWebservice searchRequest
    }

    private def invokeWebservice(SearchRequest searchRequest) {
        // Invoke webservice method.
        proxy.findAuthor searchRequest
    }

    private def createSearchRequest(String name) {
        // SearchRequest class is dynamically created by GroovyWS,
        // so we must use the proxy.create() method to create a new
        // instance.
        def searchRequest = proxy.create('com.mrhaki.groovyws.server.SearchRequest')

        // The authorName property of SearchRequest can be null, so
        // we must use JAXBElement to set the value.
        searchRequest.authorName = new JAXBElement(new QName("http://server.groovyws.mrhaki.com", "authorName"), String, name)

        searchRequest
    }

    private void createProxy() {
        if (!proxy) {
            // Use GroovyWS to create a client proxy.
            proxy = new WSClient(wsdlUrl, this.class.classLoader)

            // Make sure all required classes are created and available.
            proxy.initialize()
        }
    }

}
// File: src/test/groovy/com/mrhaki/groovyws/client/BlogWSClientSpec.groovy
package com.mrhaki.groovyws.client

import spock.lang.Specification

class BlogWSClientSpec extends Specification {

    def "get author with name mrhaki"() {
        given:
        def client = new BlogWSClient(wsdlUrl: 'http://localhost:8080/server/services/blog?wsdl')

        when:
        def author = client.findAuthor('mrhaki')

        then:
        // JAXBElement is returned, so we must use 
        // the value property of this element 
        // to get the real type and value.
        'mrhaki' == author.name.value  
        def arrayOfBlogItems = author.blogItems.value
        def blogItems = arrayOfBlogItems.blogItem
        2 == blogItems.size()
        'Title1' == blogItems[0].title.value
        'Title2' == blogItems[1].title.value
        'Sample blogitem one.' == blogItems[0].text.value
        'Sample blogitem two.' == blogItems[1].text.value
   }
}

We have all the files and are ready to run the test:

$ gradle -q test

After the test has succesfully run we can open the test report HTML file. It is a good idea to take a look at the System.err output. The output shows which classes are generated by GroovyWS dynamically:

...
Nov 11, 2010 9:09:33 PM org.apache.cxf.jaxb.JAXBUtils logGeneratedClassNames
INFO: Created classes: com.mrhaki.groovyws.server.ArrayOfBlogItem, com.mrhaki.groovyws.server.Author, com.mrhaki.groovyws.server.BlogItem, com.mrhaki.groovyws.server.FindAuthor, com.mrhaki.groovyws.server.FindAuthorResponse, com.mrhaki.groovyws.server.ObjectFactory, com.mrhaki.groovyws.server.SearchRequest, com.mrhaki.groovyws.server.This$Dist$Get$2, com.mrhaki.groovyws.server.This$Dist$Get$2Response, com.mrhaki.groovyws.server.This$Dist$Invoke$2, com.mrhaki.groovyws.server.This$Dist$Invoke$2Response, com.mrhaki.groovyws.server.This$Dist$Set$2, com.mrhaki.groovyws.server.This$Dist$Set$2Response

This code shows how easy it is to invoke a SOAP web service with GroovyWS. Even if the web service has complex objects as a reponse or as input parameter. In a future post we first see how we can configure the Grails SOAP web service so we don't need to use JAXBElement objects. This makes the client code even more readable and easier to create. And we will take a look at how we can change the logging for the generated client code.