Montag, 30. August 2010

WSDL First for Interoperability

Einen JAX-WS Web Service mit dem Java First Ansatz und entsprechenden Annotationen zu programmieren ist einfach. Der Java First Ansatz hat allerdings eine entscheidende Schwachstelle: Interoperabilität. Diese Schwachstelle liegt beim WSDL First Ansatz nicht vor. Beim WSDL First Ansatz ist ein XML-Schema und eine WSDL-Datei die gemeinsame Schnittstelle und das Typsystem zum Austauschen von Nachrichten zwischen den Services. Deshalb ist dieser Ansatz typsicherer und besser für die Kommunikation in heterogenen Systemumgebungen geeignet.

Beim WSDL First Ansatz erstellt man zunächst ein XML-Schema und eine WSDL-Datei und generiert danach mit "wsimport" das Service Endpoint Interface (SEI), welches die Basis für die Web Service Implementierung ist. In dem Blogbeitrag wird ein einfacher Web Service mit dem WSDL First Ansatz auf Basis des folgenden Schemas und WSDL-Datei erstellt.

XML-Schema des Blog Web Services - blog.xsd

XML-Schema des Blog Web Services

Graphische Darstellung und WSDL des Blog Web Services - blog.wsdl


Der Eintrag <PORT> in der WSDL ist durch den konfigurierten Port des Applikationsservers zu ersetzen, auf dem der Web Service deployed werden soll.

Die Web Service Artefakte generiert man mit Hilfe von "wsimport".

wsimport -keep blog.wsdl

Aus den generierten Web Service Artefakten entnimmt man das Service Endpoint Interface (SEI) aus dem man die Service Endpoint Implementierung (SIB) erstellt. Die Service Endpoint Implementierung entspricht dem funktionalen Web Service der in einem EJB-Projekt erstellt und in einem EAR als EJB-JAR deployed wird.

Service Endpoint Implementierung die im Package ccd.jee.ws.soap.blog zu hinterlegen ist:

import javax.ejb.Stateless;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

@WebService(name = "BlogEntryWebService",
                        targetNamespace = "http://blog.soap.ws.jee.ccd/")
@XmlSeeAlso({
ObjectFactory.class
})
@Stateless
public class BlogEntryBean {

  @WebMethod
  @WebResult
  @RequestWrapper(localName = "getBlogEntry",
                               targetNamespace = "http://blog.soap.ws.jee.ccd/",
                               className = "ccd.jee.ws.soap.blog.GetBlogEntry")
  @ResponseWrapper(localName = "getBlogEntryResponse",
                                 targetNamespace = "http://blog.soap.ws.jee.ccd/",
                                 className = "ccd.jee.ws.soap.blog.GetBlogEntryResponse")
   public String getBlogEntry(@WebParam(name = "id", targetNamespace = "")  int id) {

         printRequest(id);

        GetBlogEntryResponse response = new ObjectFactory().createGetBlogEntryResponse();
        response.setReturn("getBlogEntry - response");

        return response.getReturn();
    }

    @WebMethod
    @Oneway
    @RequestWrapper(localName = "storeBlogEntry",
                                 targetNamespace = "http://blog.soap.ws.jee.ccd/",
                                 className = "ccd.jee.ws.soap.blog.StoreBlogEntry")
     public void storeBlogEntry(@WebParam(name = "title", targetNamespace = "") String title,
                                           @WebParam(name = "entry", targetNamespace = "") String entry) {

         printRequest(title, entry);
     }

     private void printRequest(Integer id) {

        System.out.printf( "web service request - getBlogEntryById - id : %d", id);
    }

    private void printRequest(String title, String entry) {

        System.out.printf( "web service request - storeBlogEntry - title: %s / entry: %s", title, entry);
    }
}

Die beim "wsimport" Durchlauf mit dem Service Endpoint Interface generierten Artefakte kopiert man in eine eigenes JAR-Projekt in das dort erstellte Package: ccd.jee.ws.soap.blog und bindet das JAR-Projekt mit dem EJB-JAR über die Java EE Module Dependencies im EJB-JAR Projekt. Nachdem die Kompilierfehler im EJB-JAR Projekt durch das Binden beseitigt sind, kann der Blog Web Service deployed werden. Als Deploymentumgebung eignet sich der JBoss AS 6 (Milestone 3).

Nach dem Deployment des Web Services kann über die JBoss Service View die WSDL-Referenz erfragt werden. Auf Basis der WSDL-Referenz erzeugt man nun mit "wsimport" die clientseitige Service-Schnittstelle.

wsimport -keep  http://localhost:<PORT>/WsdlSoapBlogProjectEJB/BlogEntryWebService?wsdl

 Der Eintrag <PORT> beim "wsimport" Aufruf ist entsprechend des lokal eingestellten Ports des Apllikationsservers, auf dem der Blog Web Service läuft, zu ersetzen.

Den neu generierte Service Client zur Ansprache des Web Services kopiert man nun in das zuvor angelegte JAR-Projekt in das Package ccd.jee.ws.soap.blog und erstellt darauf folgend ein weiteres Java Projekt zum Testen des Web Services. 

In dem Java Projekt legt man ein Package und folgende Testklasse an:

import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;

public class TestWebService {

    private BlogEntryBeanService service;
    private BlogEntryWebService port;

    @Before
    public void setUp() {

        service = new BlogEntryBeanService();
        port = service.getBlogEntryWebServicePort();
    }

    @Test
    public void testStoreBlogEntry() {

        port.storeBlogEntry("SOAP", "SOAP is very nice");
    }

    @Test
    public void testGetBlogEntry() {

        final String response = port.getBlogEntry(1);
        assertEquals("getBlogEntry - response", response);
    }
}

Damit der Test ausgeführt werden kann, ist die JAR-Datei mit den Web Service Arteakten in den Build Pfad des Testprojektes aufzunehmen.Wenn alles gut verlaufen ist, sollte nun der JUnit-Testbalken grün sein und in der Konsole des Applikationsservers sollten zwei Meldungen erscheinen (storeBlogEntry und getBlogEntry Requests).

Der Blog Entry Web Service ist bewusst einfach gehalten worden und verwendet deshalb auch kein Backend. Das wesentliche Fazit der Erstellung des Blog Web Services ist, dass man bei der Implementierung des Services "wsimport" zweimal aufruft. Einmal zum Erstellen des Service Endpoint Interfaces, damit die Service Endpoint Implementierung auf Basis der generierten Schnittstelle erfolgen kann und nach dem Deployment des Web Services wird "wsimport" nochmals mit der deployten WSDL-Referenz aufgerufen, um die clientseitige Service Schnittstelle zu generieren, die als Proxy für einen Web Service Client dient.


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.