Etape 1 : création d'un webservice SOAP / HTTP

a) création du projet

Le serveur de webservices (WS) sera en fait une webapp qui exposera un fichier WSDL et qui répondra aux requêtes exprimées en SOAP. Pour un début nous allons faire dans l'originalité et faire un WS HelloWorld. Dans un deuxième temps, nous en ferons un plus complexe en terme de structure données.

On commence par créer un nouveau projet sous eclipse. On utilise maven pour construire et packager l'application. J'utilise la structure classique des répertoires de maven avec src/main/java et src/main/resources pour les sources et target/ pour les builds.



Dans le fichier pom.xml on y met les dépendances CXF et SL4J :
  • cxf-rt-frontend-jaxws
  • cxf-rt-transports-http
  • slf4j-api
  • slf4j-log4j12
A l'heure ou j'écris les versions suivantes sont utilisées (à mettre en fin du pom.xml) :
    <properties>
        <spring.version>2.5.6</spring.version>
        <cxf.version>2.2.9</cxf.version>
        <sl4j.version>1.6.1</sl4j.version>
  </properties>


La version de spring est donnée juste à titre indicatif car c'est celle utilisée par CXF (les librairies spring-core, spring-context, spring-beans, spring-web sont utilisées).

b) définition et implémentation du service

La définition et création du service est très rapide avec les annotations JAX-WS. L'interface est la suivante :

package fr.openfarm.helloworld.wss;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.WebParam;

@WebService
public interface HelloWorldWss {
    @WebMethod
    public String sayHi(@WebParam(name="text")String text);
}


L'annotation @WebService est l'annotation minimale. Cependant, pour éviter de rendre les paramètres anonymes dans la génération à la volée du WSDL, il est conseillé d'ajouter @WebMethod et @WebParam afin de forcer le nommage.
L'implémentation est tout aussi rapide :

package fr.openfarm.helloworld.wss;

import javax.jws.WebService;

import org.apache.log4j.Logger;

@WebService(endpointInterface = "fr.openfarm.helloworld.wss.HelloWorldWss")
public class HelloWorldWssImpl implements HelloWorldWss {
   
    static final Logger logger = Logger.getLogger(HelloWorldWssImpl.class);
   
    public String sayHi(String text) {
        StringBuilder sb = new StringBuilder("Hello");
        sb.append(text);
        logger.debug(sb.toString());
        return sb.toString();
    }

}


c) configuration Spring et CXF

Une fois cette (superbe) implémentation réalisée, il faut configurer CXF , le contexte Spring et le contexte web.
Voici le contenu du fichier src/main/resources/applicationContext.xml minimal (dans le cas d'un client comme on le verra plus loin) auquel on aura ajouté un élément jaxws:endpoint qui définira notre web service.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="
http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.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" />
   
    <jaxws:endpoint id="helloWorld"
        implementor="fr.openfarm.helloworld.wss.HelloWorldWssImpl"
        address="/HelloWorld"
        xmlns:e="http://service.jaxws.cxf.apache.org/endpoint"
        xmlns:s="http://service.jaxws.cxf.apache.org/service"/>
   
</beans>


Afin de pouvoir utiliser cette notation, il est important de bien ajouter les namespaces xmlns:jaxws et xmlns:http-conf dans l'entête. D'autre part, il ne faut pas oublier de mettre les XSD de spring 2.5 dans schemaLocation, sinon vous risquez d'obtenir cette erreur : The prefix "jaxws" for element "jaxws:endpoint" is not bound. Elle peut vite arriver si vous vous fiez à la documentation CXF qui n'est pas toujours à jour.
Les xmlns:e et xmlns:s sont ajoutés pour mémoire au cas où l'on souhaite personnaliser le nommage du endpoint et du service par l'intermédiare des attributs endpoint et service dans la balise <jaxws:endpoint>.
Les trois directives import chargent les fichiers de configuration CXF supplémentaires. Pour le moment ces fichiers n'existent pas.

d) configuration webapp et logs

Pour log4j (via API SL4L) il est nécessaire d'ajouter un fichier de configuration minimum pour les logs dans src/main/resources
log4j.rootCategory=WARN, CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%d{ABSOLUTE} %-5p %c{1}]: %m%n
log4j.logger.fr.openfarm=DEBUG

Ensuiite le fichier web.xml devra être comme suit :

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <display-name>Hello World WSS</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml
        </param-value>
    </context-param>

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

    <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>/services/*</url-pattern>
    </servlet-mapping>
   
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
       
</web-app>


La première partie permet au contexte Spring d'être chargée grâce au ContextLoaderListener auquel on indique de charger le fichier applicationContext.xml.
On déclare ensuite la servlet CXF qui va permettre d'exposer nos services derrière le contexte helloworld-wss/services/.

e) compilation et packaging de l'application avec maven

Dans le pom.xml on ajoute le plugin pour compiler le tout en Java 6 :
<pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.6</source>
                        <target>1.6</target>
                    </configuration>
                </plugin>
            </plugins>
</pluginManagement>

ensuite dans le répertoire du projet il suffit pour créer le WAR de lancer
$ mvn clean package
(ne pas oublier de mettre <packaging>war</packaging> en début du pom.xml)

f) déploiement

On peut utiliser le plugin maven pour tomcat, ou sinon voir cet article

g) utilisation du web service

Une fois la webapp déployée sous Tomcat, on peut aller à l'URL suivante http://localhost:8081/helloworld-wss/services/ pour voir ceci :



En cliquant sur le lien, on peut alors accéder au fichier WSDL qui aura l'allure suivante :

<?xml version='1.0' encoding='UTF-8'?><wsdl:definitions name="HelloWorldWssImplService" targetNamespace="http://wss.helloworld.openfarm.fr/" xmlns:ns1="http://cxf.apache.org/bindings/xformat" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://wss.helloworld.openfarm.fr/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:types>
<xs:schema elementFormDefault="unqualified" targetNamespace="http://wss.helloworld.openfarm.fr/" version="1.0" xmlns:tns="http://wss.helloworld.openfarm.fr/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="sayHi" type="tns:sayHi" />
<xs:element name="sayHiResponse" type="tns:sayHiResponse" />
<xs:complexType name="sayHi">
    <xs:sequence>
      <xs:element minOccurs="0" name="text" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
<xs:complexType name="sayHiResponse">
    <xs:sequence>
      <xs:element minOccurs="0" name="return" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>
  </wsdl:types>
  <wsdl:message name="sayHiResponse">
    <wsdl:part element="tns:sayHiResponse" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="sayHi">
    <wsdl:part element="tns:sayHi" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="HelloWorldWss">
    <wsdl:operation name="sayHi">
      <wsdl:input message="tns:sayHi" name="sayHi">
    </wsdl:input>
      <wsdl:output message="tns:sayHiResponse" name="sayHiResponse">
    </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="HelloWorldWssImplServiceSoapBinding" type="tns:HelloWorldWss">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="sayHi">
      <soap:operation soapAction="" style="document" />
      <wsdl:input name="sayHi">
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output name="sayHiResponse">
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="HelloWorldWssImplService">
    <wsdl:port binding="tns:HelloWorldWssImplServiceSoapBinding" name="HelloWorldWssImplPort">
      <soap:address location="http://localhost:8081/helloworld-wss/services/HelloWorld" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>



La sécurisation fera l'objet d'un deuxième billet.
@Bientot