Web Services leicht gemacht mit XFire

Eclipse-Plugin für XFire

Markus Demolsky

Noch vor zwei Jahren war die Entwicklung von Web Services ein unangenehmer Task eines jeden Entwicklers. Ungeachtet davon ob nun der Contract-First- oder der Code-First-Ansatz benötigt wird, Code- oder WSDL-Generierung auf Ebene der Commandozeile ist nicht gerade benutzerfreundlich. Im Rahmen dieses Artikels werde ich das SOAP Framework XFire erklären und wie das Eclipse-Plug-in beim Contract-First-Ansatz von Web Services behilflich ist.

XFire ist im Gegensatz zu Axis noch ein junges Open-Source-Framework im Bereich Web Services, welches auf eine schnelle und effiziente Entwicklung von Web Services abzielt. Zum Zeitpunkt der Erstellung dieses Artikels steht das Framework in der Version 1.2.1 auf der Website zum Download zur Verfügung. Bevor ich das Eclipse-Plug-in von XFire erkläre, werde ich einen kurzen Überblick dieser Technologie geben.

Was ist XFire?

XFire ist ein Open-Source-SOAP-Framework, welches die Entwicklung von Web Services erleichtert. Es handelt sich bei XFire um eine servlet-basierte Applikation und es muss daher in einem passenden Servlet-Container , wie zum Beispiel Tomcat, betrieben werden.

Obwohl XFire erst in der Version 1.2.1 zur Verfügung steht, bietet es bereits eine breite Palette an Funktionalität an. So werden unter anderem die wichtigsten Web-Service-Standards, wie SOAP, WSDL, WS-Adressing oder WS-Security unterstützt. Die Stärken liegen vor allem in der Performance, dank der Verwendung des neuen Streaming API for XML (StAX). Interessant ist auch die mitgelieferte Containerunterstützung von XFire, wie Spring, Pico oder Plexus. Somit hat der Entwickler weiterhin die Möglichkeit die Stärken des Containers zu nutzen.

Ein wichtiger Bereich bei der Arbeit mit Web Services ist das Data Binding Framework. Das Data Binding definiert sowohl eingehende als auch ausgehende XML-Daten auf Java-Objekte abgebildet werden. XFire unterstützt dabei JAXB 1.1, JAXB 2.0, Castor, XML Beans und Aegis Binding, wobei Letzteres das Standard-Binding von XFire ist.

XFire-Plug-in installieren

Das XFire Eclipse Plugin benötigt Eclipse 3.2 und Java 5. Am einfachsten wird das Plug-in über den Update Manager von Eclipse installiert. Dabei wird in das Menü HELP | SOFTWARE UPDATES | FIND AND INSTALL gewechselt. Hier wird nun eine neue Remote-Seite erstellt, mit dem URL: http://dist.codehaus.org/xfire/update/. Anschließend folgt man den Anweisungen und installiert das Plug-in. Nach dem Neustart von Eclipse sollte das Plug-in verfügbar sein. Bevor wir nun das Plug-in verwenden, betrachten wir zuerst einen Beispielservice, welcher ebenfalls mit XFire erstellt wird.

Kundenservice

Als begleitendes Beispiel für diesen Artikel dient ein Kundenservice (Listing 1), welcher lediglich drei Methoden anbietet. Die erste Methode loadCustomer lädt einen Kunden mit einer übergebenen ID. Die zweite Methode getPaymentStatus liefert einen Zahlungsstatus einer gegebenen Kunden-ID. Schließlich gibt es noch eine Funktion, welche eine Liste von Kunden liefert. Das Interface ist mit JSR 181-Annotations versehen, um diese Schnittstelle als Web Service zur Verfügung zu stellen. Die Implementierung des Service enthält noch die Annotation @WebService(serviceName=“CustomerService“ endpointInterface=“ICustomerService“), um den Service unter den Namen CustomerService zur Verfügung zu stellen.

Listing 1
-----------------------------------------------------

@WebService
public interface ICustomerService {
    
  @WebMethod(operationName="loadCustomer")
  public Customer loadCustomer(String customerId);
  
  @WebMethod(operationName="loadCustomerList")
  @WebResult(name="loadCustomerListResponse")
  public List loadAllCustomers();
  
  @WebMethod(operationName="getPaymentStatus")
  @WebResult(name="paymentStatus")
  public String getPaymentStatus(String customerId);
}

Mithilfe der Annotations und der passenden Service-Factory-Einstellung für XFire hat das Framework alle notwendigen Informationen, um sowohl das XML Schema als auch die WSDL zu erzeugen. Und dies geschieht beim Start des Servlet-Containers, in dem sich das XFire Servlet befindet. Der Entwickler benötigt keine zusätzlichen Kommandozeilenbefehle mehr. Wir nehmen an unser Service befindet sich unter folgender Adresse: http://localhost:8080/xfiresample/services/CustomerService?wsdl. Listing 2 zeigt die generierte WSDL unseres Kundenservice. In unserem Beispiel befindet sich auch das Schema in der WSDL-Datei.

Listing 2
----------------------------------------------------

Code-Generierung

Ausgangspunkt für die Codegenerierung sind ein leeres Eclipse-Projekt sowie die zuvor erstellte WSDL-Datei. Nach der Installation des Plug-ins kann nun der Code-Generierungs-Wizard über den Menüpunkt FILE | NEW | OTHERS… gestartet werden (Abb. 1).

Abb. 1: XFire-Codegenerator starten

Das XFire Eclipse Plugin ist in der Lage, aus einer gegebenen WSDL-Beschreibung den notwendigen Java-Code (Serviceimplementierung und Client) zu erstellen. Nach dem Start des Wizard muss der Benutzer zuerst die Lokation der WSDL angeben. Dies kann sowohl ein gültiger URL als auch eine lokal gespeicherte WSDL-Datei sein. Wir gehen davon aus, dass unser Service auf einem lokalen Tomcat-Server läuft und beziehen uns auf: http://localhost:8080/xfiresample/CustomerService?wsdl.

Das Output-Directory informiert das Plug-in darüber, wo der generierte Code abgelegt werden soll. Hier tragen wir unser leeres Eclipse-Projekt ein. Die Package-Angabe ist optional und legt fest im welchem Package der Client und der Server erzeugt werden sollen. Die derzeitige Version des XFire Plugin unterstützt leider nur das Data Binding Framework JAXB. Die Einstellungen für das Beispiel im Artikel sollten denen in Abbildung 2 entsprechen.

Abb. 2: Der XFire Code Generator Wizard

Nachdem alle Einstellungen vorgenommen wurden, kann der Code durch Betätigen des Buttons „FINISH“ generiert werden. Hier wird auch die WSDL auf Korrektheit überprüft. In unserem Fall ist die WSDL in Ordnung und der Wizard hat seine Arbeit getan. Nach dem Aktualisieren des Projektbaumes im Package Explorer von Eclipse sollte das Projekt Abbildung 3 entsprechen.

Abb. 3: Inhalt des Eclipse-Projektes nach der Codegenerierung

Unser Eclipse-Projekt ist nun mit der XFire-Umgebung versehen, erkennbar durch das „X“fire-Symbol beim Projektordner. Die XFire-Umgebung kann jedem beliebigen Projekt zugeordnet werden. Dabei wählt der User ein beliebiges Projekt aus, markiert dieses und kann anschließend über das Pop-up-Menü (rechte Maustaste) den Menüpunkt „Add xfire nature“ auswählen. Die XFire-Umgebung stellt eine Sammlung an JAR-Dateien (welche XFire benötigt) in Form einer Bibliothek zur Verfügung. Abhängig von den Funktionen, die der Entwickler von XFire verwendet, braucht man nicht immer alle JAR Files des Containers. Daher kann der Entwickler bestimmen, welche JAR-Dateien in diesen Container aktiv sein sollen. Dafür muss in die Projekteigenschaften in den Bereich XFire gewechselt werden. (RECHTE MAUSTASTE | PROPERTIES | XFIRE) (Abb. 4). Wir benötigen für unser Beispiel noch die Commons-Codec- und die Commons-HttpClient-Bibliothek, welche standarmäßig nicht markiert sind.

Abb. 4: XFire-Projekteigenschaften
Generierter Code

Neben der XFire-Umgebung wurde natürlich auch der notwendige Code vom Generator erzeugt. Im Package at.demolsky.customer.bo und demolsky.xfiresample befinden sich die im XML Schema definierten Datentypen und Nachrichten. Neben den get- und set-Methoden enthalten diese Klassen noch spezielle Annotations, welche für das Binding Framework (in unserem Fall JAXB) benötigt werden. Diese Informationen sind notwendig, um die XML-Daten auf die Java-Objekte zu mappen und umgekehrt. Sie werden also nur vom Binding Framework verwendet und sind für uns nicht interessant.

Das im Wizard definierte Package at.demolsky.xfire enthält sowohl den Service-Client als auch den Service samt dessen Implementierung. Betrachten wir zuerst das Interface ICustomerServiceImpl und dessen Implementierung CustomerServiceImpl. Das Interface weist die entsprechenden Methoden auf, welche im WSDL definiert sind. Auch das Interface wurde gegenüber dess in Listing 1 gezeigten Interface mit Annotations erweitert, etwa @SOAPBinding oder @WebParam. Die implementierten Methoden in der Klasse CustomerServiceImpl werfen bislang nur eine UnsupportedOperationException. Diese Methoden müssen nun mit der notwendigen Geschäftslogik versehen werden. Entweder wird die Logik direkt in der Klasse implementiert oder man erstellt eine neue Klasse, die von CustoemrServiceImpl erbt.

Widmen wir uns nun der Clientseite. Der generierte Client befindet sich in der Klasse CustomerServiceClient (Listing 3) ebenfalls im Package at.demmolsky.xfire. Der generierte Code bietet einen vollständigen Client an, der nicht mehr modifiziert werden muss. Es werden zum Beispiel die notwendigen Transportprotokolle für den Client definiert (siehe create0()), sowie die passenden Endpoints für einen lokalen und entfernten Zugriff bereitgestellt. Andere Komponenten im System arbeiten also nur noch mit diesem Client, um auf die Service zuzugreifen.

Listing 3
----------------------------------------------------

public class CustomerServiceClient {

    private static XFireProxyFactory proxyFactory = new XFireProxyFactory();
    private HashMap endpoints = new HashMap();
    private Service service0;

    public CustomerServiceClient() {
        create0();
        Endpoint CustomerServiceHttpPortEP = service0 .addEndpoint(new QName("demolsky/xfiresample", "CustomerServiceHttpPort"), new QName("demolsky/xfiresample", "CustomerServiceHttpBinding"), "http://localhost:8080/xfiresample/services/CustomerService");
        endpoints.put(new QName("demolsky/xfiresample", "CustomerServiceHttpPort"), CustomerServiceHttpPortEP);
        Endpoint ICustomerServiceImplLocalEndpointEP = service0 .addEndpoint(new QName("demolsky/xfiresample", "ICustomerServiceImplLocalEndpoint"), new QName("demolsky/xfiresample", "ICustomerServiceImplLocalBinding"), "xfire.local://CustomerService");
        endpoints.put(new QName("demolsky/xfiresample", "ICustomerServiceImplLocalEndpoint"), ICustomerServiceImplLocalEndpointEP);
    }

    public Object getEndpoint(Endpoint endpoint) {
        try {
            return proxyFactory.create((endpoint).getBinding(), (endpoint).getUrl());
        } catch (MalformedURLException e) {
            throw new XFireRuntimeException("Invalid URL", e);
        }
    }

    public Object getEndpoint(QName name) {
        Endpoint endpoint = ((Endpoint) endpoints.get((name)));
        if ((endpoint) == null) {
            throw new IllegalStateException("No such endpoint!");
        }
        return getEndpoint((endpoint));
    }

    public Collection getEndpoints() {
        return endpoints.values();
    }

    private void create0() {
        TransportManager tm = (org.codehaus.xfire.XFireFactory.newInstance().getXFire().getTransportManager());
        HashMap props = new HashMap();
        props.put("annotations.allow.interface", true);
        props.put("objectServiceFactory.style", "wrapped");
        AnnotationServiceFactory asf = new AnnotationServiceFactory(new Jsr181WebAnnotations(), tm, new AegisBindingProvider(new JaxbTypeRegistry()));
        asf.setBindingCreationEnabled(true);
        service0 = asf.create((at.demolsky.xfire.ICustomerServiceImpl.class), props);
        {
            AbstractSoapBinding soapBinding = asf.createSoap11Binding(service0, new QName("demolsky/xfiresample", "ICustomerServiceImplLocalBinding"), "urn:xfire:transport:local");
        }
        {
            AbstractSoapBinding soapBinding = asf.createSoap11Binding(service0, new QName("demolsky/xfiresample", "CustomerServiceHttpBinding"), "http://schemas.xmlsoap.org/soap/http");
        }
    }

    public ICustomerServiceImpl getCustomerServiceHttpPort() {
        return ((ICustomerServiceImpl)(this).getEndpoint(new QName("demolsky/xfiresample", "CustomerServiceHttpPort")));
    }

    public ICustomerServiceImpl getCustomerServiceHttpPort(String url) {
        ICustomerServiceImpl var = getCustomerServiceHttpPort();
        org.codehaus.xfire.client.Client.getInstance(var).setUrl(url);
        return var;
    }

    public ICustomerServiceImpl getICustomerServiceImplLocalEndpoint() {
        return ((ICustomerServiceImpl)(this).getEndpoint(new QName("demolsky/xfiresample", "ICustomerServiceImplLocalEndpoint")));
    }

    public ICustomerServiceImpl getICustomerServiceImplLocalEndpoint(String url) {
        ICustomerServiceImpl var = getICustomerServiceImplLocalEndpoint();
        org.codehaus.xfire.client.Client.getInstance(var).setUrl(url);
        return var;
    }

}

Erstellen wir nun einen kleinen Testfall (Listing 4), der diesen Client verwendet und den Service am Server aufruft. Wir definieren zunächst einmal einen URL, unter der unser Kundenservice ansprechbar ist. Anschließend erzeugen wir eine Instanz der generierten Clientklasse CustomerServiceClient. Alle drei Testmethoden verwenden die Methode getCustomerServiceHttpPort(url) des generierten Clients. Diese Methode erzeugt einen Client mit dem passenden ICustomerService Interface und ermöglicht es uns, auf die entfernten Service des URL zuzugreifen. Und fertig ist unsere Clientanwendung.

Listing 4
---------------------------------------------------

public class TestCustomerServiceClient extends TestCase {
    private String url = "http://localhost:8080/xfiresample/services/CustomerService";
    private CustomerServiceClient client = new CustomerServiceClient();
    
    public void testLoadCustomer(){
       Customer c = client.getCustomerServiceHttpPort(url).loadCustomer("2");
       System.out.println("Geladener Kunde: " + c.getTitle1().getValue() + " " + c.getTitle2().getValue());
    }
    
    public void testLoadAllCustomers(){
        List l = client.getCustomerServiceHttpPort(url).loadCustomerList().getCustomer();
        System.out.println(l);
    }
    
    public void testGetPaymentStatus(){
        System.out.println(client.getCustomerServiceHttpPort(url).getPaymentStatus("1"));
    }
}
Fazit

XFire ist ein sehr leistungsstarkes SOAP Framework und bietet bereits eine Menge an Funktionalität an. Leider wird in der jetzigen Plug-in-Version nur das Data Binding Framework JAXB unterstützt. Bei vielen Entwicklern punktet es vor allem dadurch, dass es leicht zu verwenden ist und auch mit Spring ideal kombiniert werden kann. Man hat ohne große Probleme einen Web Service erstellt und der Client ist auch ziemlich rasch generiert, wie dieser Artikel demonstriert hat.

Den Quellcode zum Artikel finden Sie hier.

Markus Demolsky studierte an der TU/Uni Wien. Nun ist er als Consultant und Software-Engineer bei der Firma ICODEX Software AG beschäftigt. In seiner freiberuflichen Tätigkeit unterstützt er andere Unternehmen im Bereich Softwaredesign/-architekturen und Open-Source-Technologien im Java EE-Umfeld. Aktuell arbeitet er gemeinsam mit Dr. Alexander Schatten an einem Buch zum Thema „Software Engineering – Best Practices“.
Geschrieben von
Markus Demolsky
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.