Web Service Tutorial - Erstellung eines Gästebuchs mit Web Services

Realisiert mit Eclipse, JAX-WS und Apache Tomcat 6.0

Autor: Kai Stapel
Version: 14.05.2010

Hinweis: Dieses Tutorial ist im Rahmen der Übung zur Vorlesung Entwicklung service-orientierter Architekturen und Anwendungen (SOA 2008 und SOA 2010) entstanden.

In diesem Tutorial wird beschrieben, wie ein Gästebuch Webservice unter Verwendung von Eclipse, JAX-WS und Apache Tomcat 6.0 erstellt wird. Um die Funktionsweise des Gästebuch-Web-Services zu testen, wird anschließend eine Java-Client-Applikation entwickelt, die den Webservice benutzt. Es wird der Top-Down-Ansatz (WSDL-to-Java) verfolgt, bei dem zunächst die Service-Schnittstelle definiert und anschließend basierend auf dieser Schnittstelle die Service-Implementierung erzeugt wird.

Übersicht

  1. Projekte: Service- und Client-Projekt erstellen
  2. XSD: Gästebuch-Schema erstellen
  3. WSDL: Gästebuch-Service-Schnittstelle erstellen
  4. wsimport: Generierung der Java-Klassen für Service und Client
  5. Service: Implementierung des Gästebuch-Webservice
  6. Client: Implementierung des Gästebuch-Client

Voraussetzungen

Dieses Tutorial setzt voraus, dass Sie die Java JDK, Eclipse mit der Web-Tools-Platform (WTP) (wir empfehlen die Eclipse IDE for Java EE Developers, da sie WTP bereits enthält) installiert und Eclipse korrekt mit Tomcat verbunden haben.

Tutorial

1. Projekte: Service- und Client-Projekt erstellen

  1. Neues dynamisches Web-Projekt für den Service: Erstellen Sie in Eclipse ein neues dynamisches Web-Projekt, indem sie im Kontextmenü der Projektansicht New -> Project... -> Web -> Dynamic Web Project wählen. Klicken Sie auf Next.

    Screenshot: Neues Dynamische-Web-Projekt

    Geben Sie einen Projektnamen für den Service ein, z. B. JaxWSGuestBookService. Die Target Runtime sollte auf Apache Tomcat 6.0 stehen. Falls nicht, wählen Sie Apache Tomcat 6.0 aus der Liste aus oder erstellen Sie eine neue Runtime.

    Screenshot: New-Wizard für Dynamische-Web-Projekte

  2. Neues dynamisches Web-Projekt für den Client: Erstellen Sie in Eclipse ein neues dynamisches Web-Projekt, indem sie im Kontextmenü der Projektansicht New -> Project... -> Web -> Dynamic Web Project wählen. Klicken Sie auf Next. Geben Sie einen Projektnamen für den Client ein, z. B. JaxWSGuestBookClient. Die Target Runtime sollte auf Apache Tomcat 6.0 stehen.

    Screenshot: Die beiden Projekte im Project Explorer

2. XSD: Gästebuch-Schema erstellen

  1. Neuer Ordner für XML-Dateien: Erstellen Sie einen neuen Ordner für die XML-Dateien, indem sie im Kontextmenü der Projektansicht New -> Folder wählen. Geben Sie den Namen des Ordners ein xml und klicken Sie auf Finish.

    Screenshot: XML-Ordner

  2. Neues XML-Schema: Erstellen Sie ein neues XML-Schema, indem sie im Kontextmenü des XML-Ordners New -> XML Schema wählen. Erstellen Sie ein Schema mit dem Namen GuestBook.xsd.

    Screenshot: Neues XML-Schema

  3. Schema Namespace: Ändern Sie den Schema-Namespace auf
    http://soa10.se.unihannover.de/GuestBook/Schema
    indem Sie über dem Schema (oben) mit Rechtsklick auf Show properties klicken.

    Screenshot: Schema-Namespace

  4. Gästebuch-Schema-Typ: Erstellen Sie einen Gästebuch-Schema-Typ mit dem Namen GuestBook, indem Sie über Types einen Rechtsklick machen und Add Complex Type wählen.

    Screenshot: XSD: Neuer Complex Type

    Machen Sie einen Doppelklick auf den neu erstellten Typ. Fügen Sie dem Gästebuch-Typ ein Gästebucheintrag-Element hinzu, indem sie per Rechtsklick auf GuestBook und Add Element klicken. Nennen Sie das Element entry.

    Screenshot: XSD: Neues Element

  5. Gästebuch-Eintrag-Schema-Typ: Erstellen Sie einen neuen Schema-Type mit dem Namen GuestBookEntry, indem Sie per Rechtsklick auf string -> Set Type -> New.. klicken.

    Screenshot: XSD: Set Type -> New...

    Screenshot: XSD: New Type

    Fügen Sie dem GuestBookEntry ein Attribut ID (Add Attribute) und die drei Elemente (Add Element) name, eMail und message hinzu.

    Screenshot: XSD: Gästebuch-Eintrag-Typ

  6. Multiplizität des Gästebuch-Typs anpassen: Stellen Sie die Multiplizität des Gästebuch-Typs auf 0..* (Zero or more).

    Screenshot: XSD: Multiplizität des Gästebuch-Typs setzen

    Speichern Sie das Schema und wechseln Sie in die Übersicht.

    Screenshot: XSD: Zurück zur Übersicht

    Das GuestBook.xsd-Schema sollte schließlich so aussehen:

    Screenshot: Das fertige Schema (XSD)

3. WSDL: Gästebuch-Service-Schnittstelle erstellen

  1. Neue WSDL: Erstellen Sie im XML-Ordner eine neue WSDL, indem sie im Kontextmenü des XML-Ordners New -> Other... -> Web Services -> WSDL wählen. Nennen Sie die WSDL-Datei GuestBook.wsdl. Klicken Sie auf Next. Stellen Sie den Target namespace auf
    http://soa10.se.unihannover.de/GuestBook/Service
    Lassen Sie alle anderen Einstellungen auf den Default-Werten und klicken Sie auf Finish.

    Screenshot: Neue WSDL

  2. Port-Adresse: Ändern Sie die Port-Adresse des Service auf
    http://localhost:8080/GuestBook
    indem Sie mit einem Rechtsklick auf den Port und Auswahl von Show properties die Properties-View öffnen und unter Address: die lokale Adresse einstellen. Dies ist die URL, auf der der Service später angesprochen werden kann.

    Screenshot: WSDL: Porteigenschaften

    Screenshot: WSDL: Neue Port-Adresse

  3. Service-Operationen: Erstellen Sie die abgebildeten Operationen getEntries und insertEntry mit einem Rechtsklick auf den portType GuestBook und Add Operation oder durch Umbenennen der vorhandenen Operation. Entfernen Sie bei insertEntry die Output-Nachricht.

    Screenshot: WSDL: Neue Operation

    Screenshot: WSDL: Operationen

  4. Schema-Typen der Nachrichten: Wechseln Sie in den Inline Schema Editor des getEntriesResponse-Elements der Output-Nachricht der Operation getEntries, indem Sie auf den Pfeil rechts neben der Operation klicken.

    Screenshot: WSDL: Zum Inline Schema Editor wechseln

    Suchen Sie den GuestBook-Schema-Typ aus der XSD, indem Sie auf Set Type -> Browse... klicken. Wählen Sie im darauf folgenden Menü, dass im gesamten Projekt nach Schema-Definitionen gesucht werden soll (Enclosing Project). So wird automatisch unser Gästebuch-Typ aus der XSD-Datei gefunden. Wählen Sie den Typ mit Klick auf OK. Speichern und schließen Sie den Inline-Editor.

    Screenshot: WSDL: Gästebuch-Schema-Typ suchen 1

    Screenshot: WSDL: Gästebuch-Schema-Typ suchen 2

    Wechseln Sie in den Inline Schema Editor des insertEntry-Elements der Input-Nachricht der Operation insertEntry. Setzen Sie dort das Element auf den GuestBookEntry-Type.

    Screenshot: WSDL: Gästebucheintrag-Schema-Typ suchen

  5. Binding aktualisieren: Aktualisieren Sie das Binding, indem Sie per Rechtsklick auf das Binding-Symbol und Auswahl von Generate Binding Content... den Binding-Wizard öffnen. Dort wählen Sie Overwrite existing binding information und klicken auf Finish.

    Screenshot: WSDL: Binding generieren 1

    Screenshot: WSDL: Binding generieren 2

    Damit ist die Schnittstelle definiert. Download der fertigen GuestBook.wsdl.

4. wsimport: Generierung der Java-Klassen für Service und Client

Als Nächsten müssen die Schema-Klassen, das Service-Interface und der Client-Stub generiert werden. Die generierten Klassen können sowohl für den Service als auch für den Client genutzt werden. Daher generieren wir sie zunächst im Service-Projekt und kopieren dann alles ins Client-Projekt.

  1. Generierung mit wsimport: Öffnen Sie ein Terminal und wechseln Sie in das Service-Projekt-Verzeichnis. Generieren Sie die Klassen mit folgendem Befehl:
    wsimport -Xnocompile -s src xml/GuestBook.wsdl
    
    Der Schalter -Xnocompile sorgt dafür, dass nur Java-Quellcode (.java) und keine kompilierten Klassen (.class) erzeugt werden. Der Schalter -s gibt den Zielordner an. Optional könnte man noch mit -p ein Package vorgeben, was dazu führen würde, dass alle generierten Klassen zu diesem einen Package gehören würden. Ohne diesen Parameter werden aus den Target-Namespaces Packages erzeugt, was in unserem Beispiel dafür sorgt, dass Schema- und Service-Klassen in unterschiedlichen Packages landen.

    Screenshot: wsimport-Aufruf

  2. Klassen ins Client-Projekt kopieren: Kopieren Sie alle generierten Klassen in den Source-Ordner des Client-Projekts.

    Screenshot: Klassen ins Client-Projekt kopieren

    Aktualisieren Sie die beiden Projekte in Eclipse, damit die Klassen sichtbar werden.

    Screenshot: Übsericht der generierten Klassen

5. Service: Implementierung des Gästebuch-Webservice

Jetzt muss die Service-Implementierung erstellt und einige kleine Änderungen an den Modellklassen vorgenommen werden.

  1. Gästebuch-Logik anpassen: Zunächst brauchen wir ein wenig Logik in den Modellklassen. Der Einfachheit halber speichern wir die Gästebuchinhalte nicht in einer externen Datenbank, sondern in lokalen Klassenvariablen. Dadurch ist der Inhalt des Gästebuchs nicht persistent und der Webservice wird zustandsbehaftet. Für unser Beispiel soll das aber ausreichen. In GuestBook.java im Schame-Package erzeugen Sie zuerst einen parameterlosen Konstruktor, der die Eintragsliste initialisiert.
    Auszug aus de.unihannover.se.soa10.guestbook.schema.GuestBook.java
    	public GuestBook() {
    		super();
    		entry = new ArrayList<GuestBookEntry>();
    	}
    
    Passen Sie die Methode getEntry() wie angegeben an und erstellen Sie eine neue Methode addEntry(GuestBookEntry).
    Auszug aus de.unihannover.se.soa10.guestbook.schema.GuestBook.java
    	public List<GuestBookEntry> getEntry() {
    		return this.entry;
    	}
    	public void addEntry(GuestBookEntry entry) {
    		this.entry.add(entry);
    	}
    
  2. Webservice-Implementierung: Erstellen Sie im Package de.unihannover.se.soa10.guestbook.service die Klasse GuestBookImpl. Die Klasse implementiert das Interface de.unihannover.se.soa10.guestbook.service.GuestBook.

    Screenshot: GuestBookImpl.java Wizard

    Annotieren Sie die Klassendeklaration mit
    @WebService(name = "GuestBook",
    	serviceName = "GuestBook",
    	portName = "GuestBookSOAP",
    	targetNamespace = "http://soa10.se.unihannover.de/GuestBook/Service",
    	endpointInterface = "de.unihannover.se.soa10.guestbook.service.GuestBook")
    
    Der Parameter name mapped auf wsdl:portType, serviceName auf wsdl:service, portName auf wsdl:port, targetNamespace auf den Target-Namespace dee WSDL und endpointInterface auf den Endpoint-Interface-Namen. Das Interface wurde oben mit wsimport generiert. Nach Implementierung der Methoden insertEntry und getEntries sollte die Klasse ungefähr so aussehen (inkl. Debug-Ausgaben): GuestBookImpl.java
    package de.unihannover.se.soa10.guestbook.service;
    
    import javax.jws.WebService;
    import de.unihannover.se.soa10.guestbook.schema.GuestBookEntry;
    
    @WebService(name = "GuestBook",
    		serviceName = "GuestBook",
    		portName = "GuestBookSOAP",
    		targetNamespace = "http://soa10.se.unihannover.de/GuestBook/Service",		
    		endpointInterface = "de.unihannover.se.soa10.guestbook.service.GuestBook")
    public class GuestBookImpl implements GuestBook {
    
    	private de.unihannover.se.soa10.guestbook.schema.GuestBook guestBook;
    	
    	public GuestBookImpl() {
    		super();
    		this.guestBook = new de.unihannover.se.soa10.guestbook.schema.GuestBook();
    	}
    	
    	@Override
    	public de.unihannover.se.soa10.guestbook.schema.GuestBook getEntries(
    			String parameters) {
    		System.out.println("DEBUG Info: GuestBook webservice: "
    				+ "getEntries (parameter = \""
    				+ parameters + "\") called...");
    		
    		return this.guestBook;
    	}
    
    	@Override
    	public void insertEntry(GuestBookEntry entry) {
    		System.out.println("DEBUG Info: GuestBook webservice: "
    				+ "insertEntry (ID = "
    				+ entry.getID()
    				+ ", name = \""
    				+ entry.getName()
    				+ "\", eMail = \""
    				+ entry.getEMail()
    				+ "\", message size = "
    				+ entry.getMessage().length() + ") called...");
    
    		this.guestBook.addEntry(entry);
    	}
    }
    
  3. Service starten: Um den Service ausprobieren zu können, brauchen wir einen Server, auf dem der Service läuft. Für lokale Tests bringt das JAX-WS Framework eine einfache Möglichkeit zum Ausführen eines JAX-WS-Webservice mit. Mit folgender Anweisung startet man einen Java-Standalone-Server, auf dem der Webservice läuft:
    Endpoint endpoint = Endpoint.publish("http://localhost:8080/GuestBook", new GuestBookImpl());
    
    Erstellen Sie dazu im Package de.unihannover.se.soa10.guestbook eine neue Klasse GuestBookServiceStarter mit Main-Methode.

    Screenshot: GuestBookServiceStarter.java Wizard

    Inklusive Debug-Ausgaben sieht die Main-Methode wie folgt aus:
    Auszug aus GuestBookServiceStarter.java
    	public static void main(String[] args) throws IOException {
    		GuestBook service = new GuestBookImpl();
    
    		System.out.println("DEBUG Info: GuestBook webservice: Starting webservice...");
    		Endpoint endpoint = Endpoint.publish("http://localhost:8080/GuestBook", service);
    		
    		System.out.println("DEBUG Info: GuestBook webservice: Server is running...");
    		
    		// keep server running until quit is pressed
    		JOptionPane.showOptionDialog(null,
    				"Der Service wurde gestartet.",
    				"Gästebuch-Webservice",
    				JOptionPane.CANCEL_OPTION,
    				JOptionPane.PLAIN_MESSAGE,
    				null,
    				new String[]{"Service beenden"},
    				"Service beenden");
    		
    		System.out.println("DEBUG Info: GuestBook webservice: Stopping webservice...");
    		endpoint.stop();
    	}
    
    Wenn der Service erfolgreich gestartet wurde, erscheint ein Nachrichten-Dialog. Zum Beenden des Service klicken Sie auf "Service beenden".

    Screenshot: Nachrichten-Dialog des laufenden Webservice

    Zum Testen des Service starten Sie den Web-Service-Explorer. Dies können Sie entweder über Rechtsklick auf der GuestBook.wsdl und Webservices -> Test with Web Service Explorer oder über das Launch Web Service Explorer-Icon der Java-EE-Perspektive machen.

    Screenshot: Webservice Explorer starten 1

    Screenshot: Webservice Explorer starten 2

    Falls Sie die zweite Variante gewählt haben, müssen Sie noch die zu testende WSDL angeben. Das kann entweder die Datei im Dateisystem sein, oder die dynmisch erzeugte WSDL des Servers (http://localhost:8080/GuestBook?wsdl).

    Screenshot: Webservice Explorer: WSDL eingeben

    Nun können Sie die Operationen des Gästebuch-Webservice ausprobieren. In der Konsole können Sie die Aktionen beobachten.

    Screenshot: Beispiel-Konsolen-Ausgabe

    Eine Antwort des Webservice könnte im Web Service Explorer wie folgt aussehen:

    Screenshot: Webservice Explorer: Beispiel-Antwort

    Das entspricht folgender SOAP-Nachricht:
    <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Body>
    <ns2:getEntriesResponse
      xmlns="http://soa10.se.unihannover.de/GuestBook/Schema"
      xmlns:ns2="http://soa10.se.unihannover.de/GuestBook/Service">
    	<entry ID="01">
    		<name>Kai</name> 
    		<eMail>***</eMail> 
    		<message>Hallo Tutorial Teilnehmer.</message> 
    	</entry>
    	<entry ID="02">
    		<name>Kai</name> 
    		<eMail>***</eMail> 
    		<message>Ich wünsche viel Spass.</message> 
    	</entry>
    </ns2:getEntriesResponse>
    </S:Body>
    </S:Envelope>
    
  4. Service deployen: Der Service kann auch im Web-Application-Server Tomcat deployt werden. Dazu benötigen wir die JAX-WS Runtime-Bibliotheken der JAX-WS Referenzimplementierung. Downloaden Sie die aktuelle .zip und kopieren Sie die .jar's aus jaxws-ri/lib in den WebContent/WEB-INF/lib-Ordner des JaxWSGuestBookService-Projekts. Aktualisieren Sie den Ordner WebContent/WEB-INF/lib in Eclipse.

    Screenshot: Bibliotheken der JAX-WS Referenzimplementierung

    Anschließend fügen Sie folgende Zeilen in die Datei WebContent/WEB-INF/web.xml ein
      <listener>
        <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
      </listener>
      <servlet>
        <servlet-name>JaxWsServlet</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>JaxWsServlet</servlet-name>
        <url-pattern>/*</url-pattern>
      </servlet-mapping>
    
    und erstellen Sie eine neue Datei WebContent/WEB-INF/sun-jaxws.xml mit folgendem Inhalt
    <?xml version="1.0" encoding="UTF-8"?>
    <endpoints version="2.0" xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime">
      <endpoint name="GuestBook"
        implementation="de.unihannover.se.soa10.guestbook.service.GuestBookImpl"
        url-pattern="/GuestBook" />
    </endpoints>
    
    Öffnen Sie die Servers-View und fügen Sie dem Tomcat-Server via Add and Remove... das Projekt JaxWSGuestBookService hinzu.

    Screenshot: Tomcat-Server in der Servers-View

    Screenshot: Projekt dem Tomcat-Server zuordnen

    Öffnen Sie nun die Server-Konfiguration mit Rechtsklick auf den Tomcat-Server und Open. Wechseln Sie in die Modules-View. Ändern Sie den Web-Module-Pfad von "/JaxWSGuestBookService" auf "/".

    Screenshot: Tomcat-Server-Konfiguration anpassen

    Starten Sie den Server. Der Server sollte nun unter http://localhost:8080/GuestBook den Endpoint und unter http://localhost:8080/GuestBook?wsdl die WSDL bereit stellen. Testen Sie den Webservice mit dem Web-Service-Explorer.

    Screenshot: Tomcat-Server starten

6. Client: Implementierung des Gästebuch-Client

Um die Funktionen des Webservices nutzen zu können, benötigt man einen Client. Wir erstellen zunächst einen simplen Konsolen-Client. Anschließend erstellen wir eine Gästebuch-Webseite, die die Gästebucheinträge über den Gästebuch-Webservice verwaltet.

  1. Simple Client: Falls noch nicht geschehen, generieren oder kopieren Sie zunächst die Java-Klassen zur Service Schnittstelle in den Source-Ordner des Client-Projekts. Erstellen Sie im Projekt JaxWSGuestBookClient ein neues Package mit dem Namen de.unihannover.se.soa10.guestbook.client. Erstellen Sie die Client-Klasse GuestBookClient mit einer Main-Methode in diesem Package.

    Screenshot: Übersicht über die Client-Klassen

    Mit nur zwei Aufrufen ist der Client bereit die Operationen des Webservice zu benutzen.
    	GuestBook_Service service = new GuestBook_Service();
    	GuestBook guestBook = service.getGuestBookSOAP();
    
    Einen neuen Eintrag im Gästebuch erstellt man dann zum Beispiel so:
    	GuestBookEntry guestBookEntry = new GuestBookEntry();
    	guestBookEntry.setID("ID-01");
    	guestBookEntry.setName("Kai");
    	guestBookEntry.setEMail("kai@example.org");
    	guestBookEntry.setMessage("Viele Grüße");
    	
    	guestBook.insertEntry(guestBookEntry);
    
    Eine Liste aller Gästebucheinträge erhält und durchläuft man zum Beispiel so:
    	de.unihannover.se.soa10.guestbook.schema.GuestBook entries =
    		guestBook.getEntries(null);
    		
    	for(GuestBookEntry entry : entries.getEntry()) {
    		System.out.println("DEBUG Info: GuestBook webservice client: "
    				+ " Entry = (" + entry + ")");
    	}		
    
    Für eine sinnvolle Ausgabe sollten Sie evtl. noch die toString-Methode der Klasse GuestBookEntry überschreiben. Zum Beispiel so:
    	@Override
    	public String toString() {
    		return id + ": "
    			+ name + ", "
    			+ (eMail != null ? eMail + ", " : "")
    			+ "\"" + message + "\"";
    	}		
    
    Die fertige Client-Klasse sieht dann inklusive Debug-Ausgaben so aus: GuestBookClient.java
    package de.unihannover.se.soa10.guestbook.client;
    
    import de.unihannover.se.soa10.guestbook.schema.GuestBookEntry;
    import de.unihannover.se.soa10.guestbook.service.GuestBook;
    import de.unihannover.se.soa10.guestbook.service.GuestBook_Service;
    
    public class GuestBookClient {
    
    	public static void main(String[] args) {
        	System.out.println("DEBUG Info: GuestBook webservice client: " +
        			"Creating the service stub...");
    		GuestBook_Service service = new GuestBook_Service();
    
        	System.out.println("DEBUG Info: GuestBook webservice client: " +
        			"Retrieving the port from the service: " + service + "...");
    		GuestBook guestBook = service.getGuestBookSOAP();
    
    		GuestBookEntry guestBookEntry = new GuestBookEntry();
    		guestBookEntry.setID("ID-01");
    		guestBookEntry.setName("Kai");
    		guestBookEntry.setEMail("kai@example.org");
    		guestBookEntry.setMessage("Viele Grüße.");
    		
            System.out.println("DEBUG Info: GuestBook webservice client: " +
            		"Inserting a new guest book message...");
    		guestBook.insertEntry(guestBookEntry);
    
    		guestBookEntry.setID("ID-02");
    		guestBookEntry.setName("Kai");
    		guestBookEntry.setEMail(null);
    		guestBookEntry.setMessage("Kai war hier.");
    		
            System.out.println("DEBUG Info: GuestBook webservice client: " +
            		"Inserting another guest book message...");
    		guestBook.insertEntry(guestBookEntry);
    		
            System.out.println("DEBUG Info: GuestBook webservice client: " +
            		"Retrieving the list of guest book messages...");
    		de.unihannover.se.soa10.guestbook.schema.GuestBook entries =
    			guestBook.getEntries(null);
    		
    		for(GuestBookEntry entry : entries.getEntry()) {
    			System.out.println("DEBUG Info: GuestBook webservice client: "
    					+ " Entry = (" + entry + ")");
    		}
    		
    	}
    
    }
    
    Nach Start des Servers und anschließendem Ausführen des Clients, sollten die Konsolenausgaben so aussehen.

    Screenshot: Konsolenausgabe des Servers

    Screenshot: Konsolenausgabe des Clients

  2. Client unabhängig vom lokalen Dateisystem machen: Im jetzigen Zustand ist der Client noch abhängig vom lokalen Dateisystem, da der Client-Stub GuestBook_Service von dort die WSDL-Datei lädt. z. B.:
    @WebServiceClient(name = "GuestBook",
    	targetNamespace = "http://soa10.se.unihannover.de/GuestBook/Service",
    	wsdlLocation = "file:/D:/workspaces/home/JaxWSGuestBookService/xml/GuestBook.wsdl")
    
    und
    	url = new URL(baseUrl, "file:/D:/workspaces/home/JaxWSGuestBookService/xml/GuestBook.wsdl");
    
    Ändern Sie alle Vorkommen der lokalen Referenz in http://localhost:8080/GuestBook?wsdl und speichern Sie.
    @WebServiceClient(name = "GuestBook",
    	targetNamespace = "http://soa10.se.unihannover.de/GuestBook/Service",
    	wsdlLocation = "http://localhost:8080/GuestBook?wsdl")
    
    und
    	url = new URL(baseUrl, "http://localhost:8080/GuestBook?wsdl");
    
    Nun sollte der Client unabhängig vom lokalen Dateisystem sein, bei gleicher Funktionsweise.
  3. Web Client Logik: Auch auf Client-Seite muss zuerst die Logik ein wenig angepasst werden. Wichtig ist, dass alle String-Variablen der Klasse GuestBookEntry.java nicht den Wert null annehmen dürfen, da dieser auf der Webseite als String "null" ausgegeben werden würde. Dazu erstellen Sie zunächst folgende Konstruktoren.
    	public GuestBookEntry() {
    		this("", "", "", "");
    	}
    	
    	public GuestBookEntry(String id, String name, String mail, String message) {
    		setID(id);
    		setName(name);
    		setEMail(mail);
    		setMessage(message);
    	}
    
    Die Setter sollten auch angepasst werden. Folgender Ausschnitt zeigt das beispielhaft für setName().
    	public void setName(String newName) {
    		if(newName != null) {
    			this.name = newName;
    		} else if(this.name == null) {
    			this.name = "";
    		}
    	}
    
    Damit bei der Erstellung der Seite geprüft werden kann, ob eine E-Mail-Adresse im aktuellen Eintrag vorhanden ist, brauchen wir noch die Methode isEMailSet().
    	public boolean isEMailSet() {
    		return (this.eMail != null &&
    				this.eMail.length() > 0 &&
    				isValidEMail(this.eMail));
    	}
    
    Die Methode isValidEMail() prüft, ob die angegebene E-Mail eine valide E-Mail-Adresse ist. Prinzipiell ist es wichtig, Nutzereingaben nicht ungeprüft zu übernehmen. Daher erstellen Sie folgeden Methoden.
    	public static boolean isValidID(String id) {
    		if(id == null || id.length() == 0)
    			return false;
    		return id.matches(ID_REGEXP);
    	}
    
    	public static boolean isValidName(String name) {
    		if(name == null || name.length() == 0)
    			return false;
    		return name.matches(NAME_REGEXP);
    	}
    
    	public static boolean isValidEMail(String eMailString) {
    		// an empty e-mail is also valid
    		if(eMailString == null || eMailString.length() == 0)
    			return true;
    		return eMailString.matches(EMAIL_REGEXP);
    	}
    	
    	public static boolean isValidMessage(String message) {
    		if(message == null || message.length() == 0)
    			return false;
    		return message.matches(MESSAGE_REGEXP);
    	}		
    
    Die dafür nötigen regulären Ausdrücke sind die folgenden.
    	public static final String EMAIL_REGEXP = "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+" +
    			"(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*" +
    			"@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+" +
    			"(?:[A-Za-z]{2}|com|COM|org|ORG|net|NET|gov|GOV|mil|MIL|biz|BIZ|edu|EDU|" +
    			"info|INFO|mobi|MOBI|name|NAME|aero|AERO|jobs|JOBS|museum|MUSEUM)$"; // RFC 2822 style
    	public static final String NAME_REGEXP = "[a-zA-ZäöüßÄÖÜ \\-_\\.,]{0,100}"; // alpha-numeric, max. 100 chars
    	public static final String MESSAGE_REGEXP = "[^<>\"]*"; // everything but <,>,"
    	public static final String ID_REGEXP = "[^<>\"]*"; // everything but <,>,"
    
  4. Web Client Controller: Nun benötigen wir einen Controller, der die Anfragen des Browsers entgegen nimmt, verarbeitet, den Webservie aufruft und eine Antwort für den Browser erstellt und verschickt. Erstellen Sie dazu im Package de.unihannover.se.soa10.guestbook.client ein neues Servlet mit New -> Servlet. Nennen Sie das Servlet GuestBookServlet, stellen Sie das URL-Mapping auf "/" und lassen Sie die init-Methode generieren.

    Screenshot: Servlet erstellen 1

    Screenshot: Servlet erstellen 2

    Screenshot: Servlet erstellen 3

    Screenshot: Servlet erstellen 4

    Die Webservice-Aufrufe erledigen die folgenden Methoden.
    	private void getGuestBookEntries() {
    		// reset guest book entries
    		guestBookList.clear();
    		
    		GuestBook_Service service = new GuestBook_Service();
    		GuestBook guestBook = service.getGuestBookSOAP();
    		
    		de.unihannover.se.soa10.guestbook.schema.GuestBook entries =
    			guestBook.getEntries(null);			
    		
    		if(entries.getEntry() != null && entries.getEntry().size() > 0) {
    			for(GuestBookEntry entry : entries.getEntry()) {
    				guestBookList.add(entry);
    			}
    		}
    	}
    	
    	private void insertGuestBookEntry(GuestBookEntry entry) {
    		GuestBook_Service service = new GuestBook_Service();
    		GuestBook guestBook = service.getGuestBookSOAP();
    		
    		entry.setID(generateID());
    		
    		guestBook.insertEntry(entry);
    	}
    
    Das komplette Servlet sieht dann wie folgt aus:
    GuestBookServlet.java
    package de.unihannover.se.soa10.guestbook.client;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import de.unihannover.se.soa10.guestbook.client.ErrorHandler;
    import de.unihannover.se.soa10.guestbook.schema.GuestBookEntry;
    import de.unihannover.se.soa10.guestbook.service.GuestBook;
    import de.unihannover.se.soa10.guestbook.service.GuestBook_Service;
    
    public class GuestBookServlet extends HttpServlet {
    	private static final String JSP_ENDING = ".jsp";
    	private static final String JSP_BASE = "/WEB-INF/jsp/";
    	private static final long serialVersionUID = 1L;
        
    	private List<GuestBookEntry> guestBookList;
    	private ErrorHandler errorHandler;
    	
    	private Integer id;
    	
    	public void init(ServletConfig config) throws ServletException {
    		super.init(config);
    
    		// initialize guest book list
    		guestBookList = new ArrayList<GuestBookEntry>();
    		
    		// initialize error handler
    		errorHandler = new ErrorHandler();
    		
    		id = new Integer(0);
    	}
    
    	protected void doGet(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		// forward request to doPost
    		doPost(request, response);
    	}
    
    	protected void doPost(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		request.setCharacterEncoding("UTF-8");
    	
    		Map parameters = request.getParameterMap();
    
    		// reset error handler
    		errorHandler.reset();
    		
    		// attach error handler to request
    		request.setAttribute("error", errorHandler);
    		
    		// process data sent
    
    		// is data from new entry JSP?
    		if(parameters.get("newEntry") != null) {
    			GuestBookEntry entry = inserNewGuestBookEntry(request);
    			if(errorHandler.isError()) {
    				// if there is an error add error handler before dispatching to jsp
    				request.setAttribute("error", errorHandler);
    			}
    			// attach current entry for old values
    			request.setAttribute("entry", entry);
    		}
    		
    		gotoJSP(request, response);
    	}
    
    	private void gotoJSP(HttpServletRequest request,
    			HttpServletResponse response) throws ServletException, IOException {
    		// forward to index.jsp
    		getGuestBookEntries();
    		request.setAttribute("guestbook", guestBookList);
    		dispatchToJSP("index", request, response);
    	}
    	
    	private GuestBookEntry inserNewGuestBookEntry(HttpServletRequest request) {
    		GuestBookEntry entry = new GuestBookEntry(); 
    		
    		Map parameters = request.getParameterMap();
    
    		String name = ((String[]) parameters.get("name"))[0];
    		if(!GuestBookEntry.isValidName(name)) {
    			errorHandler.addErrorMessage("Geben Sie Ihren Namen ein.");
    		}
    		entry.setName(name);
    		
    		String mail = ((String[]) parameters.get("email"))[0];
    		if(!GuestBookEntry.isValidEMail(mail)) {
    			errorHandler.addErrorMessage("Wenn Sie eine E-Mail angeben, dann bitte eine korrekte.");
    		}
    		entry.setEMail(mail);
    		
    		String message = ((String[]) parameters.get("message"))[0]; 
    		if(!GuestBookEntry.isValidMessage(message)) {
    			errorHandler.addErrorMessage("Geben Sie eine Nachricht ein.");
    		}
    		entry.setMessage(message);
    		
    		// if no error was found
    		if(!errorHandler.isError()) {
    			// use webservice to insert new entry into webservice-guestbook
    			insertGuestBookEntry(entry);
    		}
    		
    		return entry;
    	}
    	
    	private void insertGuestBookEntry(GuestBookEntry entry) {
    		GuestBook_Service service = new GuestBook_Service();
    		GuestBook guestBook = service.getGuestBookSOAP();
    		
    		entry.setID(generateID());
    		
    		guestBook.insertEntry(entry);
    	}
    
    	private String generateID() {
    		id++;
    		return "ID-"+id;
    	}
    
    	private void getGuestBookEntries() {
    		// reset guest book entries
    		guestBookList.clear();
    		
    		GuestBook_Service service = new GuestBook_Service();
    		GuestBook guestBook = service.getGuestBookSOAP();
    		
    		de.unihannover.se.soa10.guestbook.schema.GuestBook entries =
    			guestBook.getEntries(null);			
    		
    		if(entries.getEntry() != null && entries.getEntry().size() > 0) {
    			for(GuestBookEntry entry : entries.getEntry()) {
    				guestBookList.add(entry);
    			}
    		}
    	}
    
    	private void dispatchToJSP(String jsp, HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		RequestDispatcher r = request.getRequestDispatcher(JSP_BASE + jsp + JSP_ENDING);
    		r.forward(request, response);				
    	}	
    }		
    
    Das Servlet, und später die JSP, benötigen zur Behandlung von Fehlermedlungen noch die Klasse ErrorHandler.java:
    package de.unihannover.se.soa10.guestbook.client;
    
    import java.util.List;
    import java.util.Vector;
    
    public class ErrorHandler {
    	private List<String> errorMessages;
    	
    	public ErrorHandler() {
    		this.errorMessages = new Vector<String>();
    	}
    	
    	public List<String> getErrorMessages() {
    		return errorMessages;
    	}
    
    	public void addErrorMessage(String error) {
    		this.errorMessages.add(error);
    	}
    	
    	public void reset() {
    		this.errorMessages.clear();
    	}
    	
    	public boolean isError() {
    		return (errorMessages != null && errorMessages.size() > 0);
    	}
    }
    
    Nun muss noch die WebContent/WEB-INF/web.xml angepasst werden. Der New-Servlet-Wizard sollte schon das korrekte Servlet-Mapping erstellt haben. Zusätzlich brauchen wir noch ein paar Ausnahmen, damit Bilder- und Stylesheet-Requests nicht über das Servlet geleitet werden. Die web.xml sollte folgende Teile enthalten.
      <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.css</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.jpg</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.gif</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.png</url-pattern>
      </servlet-mapping>
      <servlet>
        <description></description>
        <display-name>GuestBookServlet</display-name>
        <servlet-name>GuestBookServlet</servlet-name>
        <servlet-class>de.unihannover.se.soa10.guestbook.client.GuestBookServlet</servlet-class>
      </servlet>
      <servlet-mapping>
        <servlet-name>GuestBookServlet</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
  5. Web Client View: Zur Erzeugung der View erstellen wir eine JSP. Dazu erstellen Sie zunächst den Ordner WebContent/WEB-INF/jsp. In diesem Ordner erstellen Sie dann die index.jsp mit folgendem Inhalt:
    index.jsp
    <?xml version="1.0" encoding="UTF-8" ?>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    
    <%@page import="java.util.List"%>
    <%@page import="de.unihannover.se.soa10.guestbook.schema.GuestBookEntry"%>
    
    <jsp:useBean id="entry" class="de.unihannover.se.soa10.guestbook.schema.GuestBookEntry" scope="request" />
    <jsp:useBean id="error" class="de.unihannover.se.soa10.guestbook.client.ErrorHandler" scope="request" />
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" href="style.css" />
    <title>Webservice-Gästebuch</title>
    </head>
    <body>
    <div id="inhalt">
    	<h1>Gästebuch</h1>
    	<h2>- mit Webservices -</h2>
    
    	<div id="new-entry">
    	<h2>Neuer Eintrag</h2>
    	<% if(error.isError()) { %>
    	<ul class="error"> 
    		<% for(String e : error.getErrorMessages()) { %>
    		<li><%= e %></li>
    		<% } %>
    	</ul>
    	<% } %>
    	<form action="." method="post">
    	<table>
    		<tr>
    			<td>Name<sup>*</sup></td>
    			<td><input type="text" name="name" maxlength="255" value="<%= entry.getName() %>" /></td>
    		</tr>
    		<tr>
    			<td>E-Mail</td>
    			<td><input type="text" name="email" maxlength="255" value="<%= entry.getEMail() %>" /></td>
    		</tr>
    		<tr>
    			<td>Nachricht<sup>*</sup></td>
    			<td><textarea cols="23" rows="4" name="message"><%= entry.getMessage() %></textarea></td>
    		</tr>
    		<tr>
    			<td> </td>
    			<td><input name="newEntry" type="submit" value="eintragen"/></td>
    		</tr>
    	</table>
    	<p><sup>*</sup> Pflichtfeld</p>		
    	</form>
    	</div>
    	
    	<h2>Nachrichten</h2>
    	<% 	List<GuestBookEntry> entries = (List<GuestBookEntry>)request.getAttribute("guestbook");
    		if(entries != null && entries.size() > 0) { %>
    		<table id="guestbook">
    		<% for(GuestBookEntry gbe : entries) { %>
    		<tr>
    			<td class="name">
    			<% if(gbe.isEMailSet()) { %>
    				<a href="mailto:<%= gbe.getEMail() %>">
    			<% } %>
    			<%= gbe.getName() %>
    			<% if(gbe.isEMailSet()) { %>
    				</a>
    			<% } %>
    			</td>
    			<td class="message"><%= gbe.getMessage() %></td>
    		</tr>
    	<% } %>
    		</table>
    	<% } else { %>
    		<p>Noch keine Einträge vorhanden.</p>
    	<% } %>
    </div>
    </body>
    </html>
    
    Style-Informationen legen wir in ein externes Stylesheet. Achtung: Die Datei style.css darf nicht im Ordner WebContent/WEB-INF, sondern sollte direkt in WebContent liegen, da sie sonst vom Browser aus nicht erreichbar ist.
    WebContent/style.css
    body {
    	font: normal 11px Verdana, Arial, sans-serif;
    	background: #EEEEEE;
        text-align: center;  /* Zentrierung im Internet Explorer */
    }
    
    h1 {
    	margin-top: 0;
    }
    
    h2 {
    	margin-top: 1em;
    	margin-bottom: 1em;
    }
    
    #inhalt {
    	background: #FFFFFF;
    	border: 1px solid #AAAAAA;
    	padding: 1em;
    	width: 600px;
    	text-align: left;    /* Seiteninhalt wieder links ausrichten */
    	margin: 0 auto;      /* standardkonforme horizontale Zentrierung */
    }
    
    table#guestbook  {
    	width: 100%;
    }
    
    table#guestbook td.name {
    	padding: 0.5em;
    	text-align: right;
    	width: 8em;
    }
    
    table#guestbook td.message {
    	padding: 0.5em;
    	background-color: #EEEEEE;
    	border: 1px solid #AAAAAA;
    	width: *;
    }
    
    .error {
    	color: red;
    }
    
    #new-entry {
    	background: #EEEEEE;
    	padding: 1em;
    	margin-top: 1em;
    	border: 1px solid #AAAAAA;
    	
    }
    
    Kopieren Sie abschließend die JAX-WS-Jars aus JaxWSGuestBookServer/WebContent/WEB-INF/lib in JaxWSGuestBookClient/WebContent/WEB-INF/lib, damit die Webservice-Anbindung klappt. Fügen Sie nun das Client-Projekt dem Tomcat-Server hinzu und starten sie Tomcat. Stellen Sie sicher, dass auch der Server mit gestartet wird. Wenn alles funktioniert sollten Sie unter der URL http://localhost:8080/JaxWSGuestBookClient/ eine funktionierende Gästebuch-Webseite sehen, die den Gästebuch-Webservice nutzt, um die Einträge zu verwalten.

    Screenshot: Client und Service sind auf dem Tomcat-Server deployt.

    Screenshot: Der Web-Client in Action.

Eclipse-Projekte

Das fertige JAX-WS Gästebuch Web Service Beispiel und der Java-Client kann direkt in Eclipse als Projekt importiert werden. Klicken Sie dazu jeweils auf Import -> General -> Existing Projects into Workspace -> Select Archive File....

Einige wichtige Dateien können hier auch direkt geladen werden:

Server

Client

Weitere Tutorials des Fachgebiets


Letzte Änderung: 06.06.2011 von Kai Stapel