Web service client genereren met Maven

March 12th, 2013 | 9 min read | Maven, SOAP, Spring, Spring MVC, Web, Web services, WSDL

Ik heb nu al enkele tutorials gesproken over REST services, maar in deze tutorial ga ik een web service (SOAP) aanspreken. Het handige van zo’n web services is dat ze contract-based zijn, je weet dus precies op voorhand (dankzij documenten zoals een WSDL) wat je moet meegeven en wat je kan terug verwachten.

Doordat SOAP/WSDL een standaard is die zeer populair is, kan je in de meeste moderne programmeertalen wel een client laten genereren vanuit een WSDL, waardoor je dus gemakkelijk een web service kan schrijven in Java en een client in C#.

In deze tutorial ga ik een weather web service gebruiken om te demonstreren hoe je een client kan genereren vanuit een WSDL dankzij Maven.

Heads up!

In deze tutorial wordt met regelmaat verwezen naar mijn vorige Java tutorials. Het is dus handig dat je deze (zeker de eerste) eens doorneemt.

Project opzetten

Net zoals in alle andere tutorials ga ik beginnen met een web application. Hoe je zo’n web applicatie project opzet heb ik in mijn eerste tutorial rond Spring al uitgelegd.

Waar je op moet letten is dat je de packages

be.g00glen00b.controller

,

be.g00glen00b.service

,

be.g00glen00b.service.impl

,

be.g00glen00b.domain

en

be.g00glen00b.soap

aanmaakt.
In de controller-package maak je een klasse

MainController

aan, in de service-package een interface

WeatherService

, in de service implementatie package maak je een klasse

WeatherServiceImpl

aan die deze interface implementeert en in de domain-package een klasse

CurrentWeather

.

Daarnaast moet je er ook voor zorgen dat je een view

index.jsp

en een view

result.jsp

hebt.

Je project structuur moet er uiteindelijk zo uit komen te zien als in onderstaande afbeelding.

basic-project-structure

Maven configuratie (client genereren)

Het belangrijkste is dat we in deze tutorial Maven gaan gebruiken om een WSDL client te genereren. Hiervoor gaan we een CXF plugin gebruiken die er zo uit ziet:

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>2.7.3</version>
    <executions>
        <execution>
            <id>process-sources</id>
            <phase>generate-sources</phase>
            <configuration>
                <wsdlOptions>
                    <wsdlOption>
                        <wsdl>http://www.webservicex.net/globalweather.asmx?WSDL</wsdl>
                        <extraargs>
                            <extraarg>-p</extraarg>
                            <extraarg>be.g00glen00b.soap</extraarg>
                        </extraargs>
                    </wsdlOption>
                </wsdlOptions>
            </configuration>
            <goals>
                <goal>wsdl2java</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Uiteraard hebben we ook het Spring framework nodig en de JSTL library, die we via volgende dependencies kunnen laden:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.1.2.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>

<!-- Web -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

Indien je dit gedaan hebt klik je met je rechtermuisknop op het project en kies je voor Run as… en dan Maven generate-sources. Hiermee ga je eigenlijk de Web service client genereren op basis van de WSDL die we in de plugin meegaven, namelijk http://www.webservicex.net/globalweather.asmx?WSDL.

Je zal merken dat als Maven klaar is, je in je target map een map generated-sources kan terug vinden met daarin de gegenereerde client. Kopieer de klasses die je daar vindt naar de package

be.g00glen00b.soap

.

copy-generated-sources

Web descriptor

De web descriptor gaan we zoals gewoonlijk de Spring dispatcherservlet gebruiken. De code hiervoor is ook dezelfde als in alle andere tutorials:

<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

Spring configuratie (springmvc-servlet.xml)

De Spring configuratie in dit voorbeeld is vrij eenvoudig, naast de

viewResolver

die nodig is voor het Spring MVC framework hebben we enkel nog enkele beans gedefinieerd die de web service client gaan initialiseren. De code hiervoor is:

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="weatherSoapService" class="be.g00glen00b.soap.GlobalWeather"
        scope="singleton" />

    <bean id="weatherSoapServiceEndPoint" class="be.g00glen00b.soap.GlobalWeatherSoap"
        factory-bean="weatherSoapService" factory-method="getGlobalWeatherSoap"
        scope="singleton" />

    <context:component-scan base-package="be.g00glen00b.controller" />
    <context:component-scan base-package="be.g00glen00b.service" />
    <context:annotation-config />

</beans>

De klasse

GlobalWeather

is eigenlijk het aanspreekpunt en erft over van de JAX-WS

Service

.

GlobalWeatherSoap

daarentegen is onze endpoint en gaan we gebruiken om calls te verrichten naar de web service.

Deze kunnen we via een getter uit de GlobalWeather service klasse halen, wat dus eigenlijk ook hetgene is wat we doen via de attributen

factory-bean

en

factory-method

.

Service

De volgende stap is dat we een service interface en implementatie gaan schrijven. De interface

WeatherService

ziet er uit als:

package be.g00glen00b.service;

import javax.xml.bind.JAXBException;

import be.g00glen00b.domain.CurrentWeather;

public interface WeatherService {

    CurrentWeather getCurrentWeather(String country, String city) throws JAXBException;
}

Niet echt zo ingewikkeld, het enige wat we hier doen is dat we een stad + land nodig hebben en op basis daarvan het weer gaan bepalen. Het enige probleem is dat de web service XML terug geeft in de vorm van een String. We kunnen deze uiteraard zelf parsen dankzij JAXB, vandaar dus ook de exceptie die gethrowed kan worden.

De implementatie van de service is ook vrij eenvoudig:

package be.g00glen00b.service.impl;

import java.io.StringReader;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import be.g00glen00b.domain.CurrentWeather;
import be.g00glen00b.service.WeatherService;
import be.g00glen00b.soap.GlobalWeatherSoap;

@Component("weatherService")
public class WeatherServiceImpl implements WeatherService {

    @Autowired
    private GlobalWeatherSoap soapService;

    public CurrentWeather getCurrentWeather(String country, String city) throws JAXBException {
        String weatherStr = soapService.getWeather(city, country);

        JAXBContext jaxbContext = JAXBContext.newInstance(CurrentWeather.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

        StringReader reader = new StringReader(weatherStr);
        return (CurrentWeather) unmarshaller.unmarshal(reader);
    }

}

Zoals je kan zien wordt de web service client hier gebruikt en wordt de String die we terug krijgen (

weatherStr

) via JAXB geparsed tot een object.

Domein object

Zoals net al gezegd, we hebben JAXB nodig om de String te parsen tot een object. De klasse die we hiervoor gaan gebruiken gaan we zelf schrijven en noemt CurrentWeather. De implementatie hiervan is een vrij eenvoudige POJO met enkele JAXB annotaties zoals we eerder al gezien hebben in m’n tutorial over RESTEasy.

package be.g00glen00b.domain;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="CurrentWeather")
public class CurrentWeather {

    private String location;
    private String time;
    private String visibility;
    private String temperature;
    private String dewPoint;
    private String relativeHumidity;
    private String pressure;
    private String status;

    public CurrentWeather() {

    }

    @XmlElement(name="Location")
    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    @XmlElement(name="Time")
    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    @XmlElement(name="Visibility")
    public String getVisibility() {
        return visibility;
    }

    public void setVisibility(String visibility) {
        this.visibility = visibility;
    }

    @XmlElement(name="Temperature")
    public String getTemperature() {
        return temperature;
    }

    public void setTemperature(String temperature) {
        this.temperature = temperature;
    }

    @XmlElement(name="DewPoint")
    public String getDewPoint() {
        return dewPoint;
    }

    public void setDewPoint(String dewPoint) {
        this.dewPoint = dewPoint;
    }

    @XmlElement(name="RelativeHumidity")
    public String getRelativeHumidity() {
        return relativeHumidity;
    }

    public void setRelativeHumidity(String relativeHumidity) {
        this.relativeHumidity = relativeHumidity;
    }

    @XmlElement(name="Pressure")
    public String getPressure() {
        return pressure;
    }

    public void setPressure(String pressure) {
        this.pressure = pressure;
    }

    @XmlElement(name="Status")
    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}

Controller

Uiteraard hebben we ook een controller nodig. In deze web applicatie gaan we de gebruiker een f orm tonen waarin hij het land + stad kan invoeren en waarop basis daarvan het weerbericht getoond zal worden. Hiervoor hebben we twee methodes, de methode om de initiële form te tonen en de methode die uitgevoerd wordt wanneer de  form verzonden wordt en het weerbericht getoond moet worden.

De code hiervoor is:

package be.g00glen00b.controller;

import javax.xml.bind.JAXBException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import be.g00glen00b.service.WeatherService;

@Controller
public class MainController {

    @Autowired
    private WeatherService weatherService;

    @RequestMapping(value = "/index.html", method=RequestMethod.GET)
    public String getIndex() {
        return "index";
    }

    @RequestMapping(value = "/index.html", method=RequestMethod.POST)
    public String postIndex(@RequestParam("country") String country, @RequestParam("city") String city, ModelMap model) {
        model.addAttribute("country", country);
        model.addAttribute("city", city);
        try {
            model.addAttribute("weather", weatherService.getCurrentWeather(country, city));
        } catch (JAXBException ex) {
            model.addAttribute("error", "Problemen met het uitlezen van de response");
        }
        return "result";
    }
}

Zoals je kan zien staan hier niet teveel speciale zaken. We roepen hier enkel de service aan die we eerder geschreven hebben en dat is het dan ook.

Index JSP pagina

Ten slotte zijn we aan de views aanbeland. De eerste view bestaat uit een eenvoudige HTML form die we gaan tonen aan de gebruiker.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
    <form name="weather" action="index.html" method="post">
        <label for="country">Land:</label> <input type="text" name="country" /><br />
        <label for="city">Stad:</label> <input type="text" name="city" /><br />
        <input type="submit" value="Weerbericht" />
    </form>
</body>
</html>

Result JSP pagina

De tweede view is de view die het resultaat zal bevatten. Ook deze view is vrij eenvoudig en bestaat voornamelijk uit JSTL om de model attributen te tonen.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
    <c:if test="${not empty error}">
        <h1>${error}</h1>
    </c:if>

    <c:choose>
        <c:when test="${weather.status == 'Success'}">
            <h1>${weather.location}</h1>
            <dl>
                <dt>Temperatuur:</dt>
                <dd>${weather.temperature}</dd>
            </dl>
            <dl>
                <dt>Zichtbaarheid:</dt>
                <dd>${weather.visibility}</dd>
            </dl>
            <dl>
                <dt>Vochtigheid:</dt>
                <dd>${weather.relativeHumidity}</dd>
            </dl>
            <dl>
                <dt>Druk:</dt>
                <dd>${weather.pressure}</dd>
            </dl>
        </c:when>
        <c:otherwise>
            <h1>Het weerbericht kon niet worden gevonden voor ${country},
                ${city}</h1>
        </c:otherwise>
    </c:choose>
</body>
</html>

Builden

Het laatste wat we nu nog moeten doen alvorens we kunnen deployen is dat we de package be.g00glen00b.soap mogen verwijderen. Maven zal ervoor zorgen dat steeds de meest recente versie van deze package in de WAR geplaatst wordt op basis van de WSDL. Het is dus niet nodig om deze package nog te hebben.

Nadien kan je je project builden en deployen met Maven met onderstaand commando:

mvn tomcat:run

Als je nu het project in je browser opent zal je een form te zien krijgen. Voer hier bij land “Belgium” in en bij stad “Antwerp” en je zal onderstaand resultaat te zien krijgen.

result

Daarmee is deze tutorial dan ook afgerond. Ik zal in de loop van de komende dagen het project tevens op SVN sharen, maar de download is alvast hieronder te vinden.

Download project

Download – wsdl-client-example.tar.gz (11 kB)

Back to tutorialsContact me on TwitterDiscuss on Twitter

Profile picture

Dimitri "g00glen00b" Mestdagh is a consultant at Cronos and tech lead at Aquafin. Usually you can find him trying out new libraries and technologies. Loves both Java and JavaScript.