WebServices

Aus iDempiere de
Zur Navigation springen Zur Suche springen

iDempiere bietet eine WebServices-Schnittstelle. Diese stellt technisch betrachtet eine volle API zum Zugriff auf alle Funktionen des Programms dar und steht damit in der Architektur parallel zu den anderen Oberfächen wie ZK, Swing und Mobile. Allerdings ist die WebServices-Schnittstelle dazu gedacht, von einem Programm bedient zu werden. Über diesen Weg kann ein anderes Programm, z.B. ein Webshop, auf iDempiere zugreifen und direkt Bestellungen ins System einspeisen.

Diese Seite zeigt, wo man Informationen über die iDempiere WebServices bekommt. Ich zeige, wie man prüft, ob der WebServices Server aktiv ist. Dann greife ich auf diesen mit Hilfe des Programms SoapUI zu, das uns hilft, die richtige XML-Datei zu erzeugen. Diese Datei kann dann auch mit wget benutzt werden. Danach zeige ich, wie man durch ein Java-Programm auf die WebServices zugreift.


vorhandene Dokumentation

Seit der ursprünglichen Implementierung in ADempiere hat sich an Aufbau und Möglichkeiten dieser Schnittstelle einiges geändert. Für diese ursprüngliche Schnittstelle gibt es auch ein aus ausführliches Kapitel im ADempiere Cookbook. Dieses Kapitel half mir ein wenig, die Grundlagen zu verstehen. Die eigentliche neue Schnittstelle ist aber auf der Seite [:en:NF1.0_Web_Services_Improvements] erklärt.

Etwas Grundlagenwissen zu WebServices kann auch nicht schaden. iDempiere benutzt WSDL, um seine Schnittstelle zu beschreiben, so das entspreochende Client-Bibliotheken automatisch darauf zugreifen können. Die Kommunikation einzelner Aufrufmethoden geschieht dann über SOAP.


erste Schritte

Da Dokumentation zu diesem Thema rar ist, erkläre ich hier, wie ich vorgegangen bin. Als erstes muss man sicherstellen, das das WebServices-Plugin überhaupt aktiv ist. Das kann man mittels "ss"-Kommando auf der OSGI-Konsole sehen oder (einfacher), indem man die Felix Console aufruft (unter meinservername/osgi/system/console) und in der Liste der Plugins nachsieht. Dort kann man das WebServices-Plugin dann auch ggf. direkt aktivieren.

Als Beispiel für die folgenden Abläufe nehme ich den iDempiere Demo-Server. Dessen Felix Console erreicht man über einen Link auf der Hauptseite (also auf https://demo.idempiere.com), das ist dann die URL https://demo.idempiere.com/osgi/system/console/

Zugriff per Webbrowser

Wie im ursprünglichen ADempiere-Modul ist die URL zum Zugriff auf die WebServices "/ADInterface/services". Ruft man diese URL (https://demo.idempiere.com/ADInterface/services) direkt auf, so erhält man eine menschenlesbare Übersicht über die WebServices.

Aus der ursprünglichen ADempire-Dokumentation (und dem Cookbook) erfährt man, das der zu benutzende WebService "ADService" heisst. Diesen gibt es inzwischen nicht mehr. Wie man auf der angezeigten Seite sieht, gibt es zwei Services namens CompositeService und ModelADService. Außerdem ist eine REST-Schnittstelle aufgeführt.

Wer den auf dieser Übersichtsseite stehenden Links folgen möchte, sollte beachten, das der Demo-Server offensichtlich hinter einem Proxy verborgen ist. Die von ihm angegebenen Pfade verweisen nämlich auf "localhost:8080/" anstatt auf "https://demo.idempiere.com/". Diese muss man also ggf. abändern, wenn man sich die WSDL-Beschreibungen der Services ansehen möchte. Die WSDL-Beschreibung des ModelADService ist also zu finden unter https://demo.idempiere.com/ADInterface/services/ModelADService?wsdl Hier kann man mal reinsehen und sich so einen Überblick verschaffen, was der WebService für Operationen erlaubt.


Einrichtung in iDempiere: Ein Beispiel

Was genau die WebServices an Daten herausrücken und an Änderungen erlauben, kann( und muss) einzeln konfiguriert werden. Hierzu geht man in das Fenster "WebServices". In der GardenWorld sind hier zwei Beispiele vorkonfiguriert. Ich möchte hier erklären, wie man einen eigenen Service konfiguriert. Ich möchte einen WebService erzeugen, der mir Zugriff auf ein Telefonbuch gewährt.

Als erstes lege ich hierzu einen Datensatz an, der den Namen meines WebService definiert. Ich entscheide mich dazu, den Namen "GetContacts" in die Felder für Value (Search Key) und Name einzutragen. Als "Web Service" nehme ich die "Model Oriented Web Services". (Dieses ist die ältere Zugriffsart. Die Alternative des "CompositeInterface" ist vor allem interessant, um mehrere Änderungen als Datenbank-Transaktion vorzunehmen. Man kann so sicherstellen, das diese entweder komplett oder gar nicht ausgeführt werden.) Als "Web Service Method" wähle ich "Query Data", weil ich ja eine Datenabfrage durchführen möchte. Als Tabelle gebe ich "AD_User" an, weil da meine Kontaktdaten drinstehen.

Im Tab "WebService Parameters" kann ich nun die Parameter angeben, die die "Query Data" Methode haben möchte. Welche das sind, kann man einerseits ganz gut in SoapUI sehen. Andererseits holt dieses seine Informationen natürlich aus der WSDL-Definition der WebService-Methode. Dort findet man z.B. für die QueryData-Methode die Angabe, das sie einen ModelCRUDRequest als Parameter hat und dieser wiederum besteht aus einem ModelCRUD- und einem ADLoginRequest-Element. Ersteres enthält nun die (nicht-optionalen) Parameter serviceType, TableName und RecordID. Mit dem "optional" ist das so eine Sache, weil ModelCRUD für alle CRUD-Operationen (Create-Read-Update-Delete) benutzt wird. Ausgerechnet in unserem Beispiel "query" wird daher das Feld "RecordID" wohl nicht benutzt, da ein Query ja u.U. mehr als einen Record ergeben soll.

Also lege ich einen Parameter TableName an, der als Konstante immer "AD_User" ist und einen freien Parameter "RecordID". Übrigens muss man diese im Request gar nicht angeben, wenn man sie als Konstante definiert (inwiefern das eine ordentlicher SOAP-Client doch validiert, kann ich aber nicht sagen). Also optionalen Parameter erzeuge ich allerdings einen Eintrag für "Filter". Ich habe ihn auf "Free" gesetzt und kann so im SOAP-Aufruf eine Zeile wie

 <_0:Filter>phone IS NOT NULL</_0:Filter>

benutzen. Natürlich kann man das auch als Kontante machen (was letztlich für dieses Beispiel wohl auch sinnvoller wäre, aber ich wollte es so probieren).

Dann gehe ich in das Tab "Web Service Field Output". Dort kann ich die Spalten angeben, die mein WebService liefert. Für ein Telefonbuch lege ich also zwei Einträge für "Name" und "Phone" an. Wer mit den Daten etwas ernstes vorhat, möchte vielleicht auch noch AD_User_ID und/oder AD_User_UU ausgeben, da man die Daten sonst bei mehrmaligem Abruf nicht mehr sich selbst zuordnen kann.

Also letztes muss man noch den Zugriff auf den WebService an eine Rolle geben. Im Beispiel kann man dazu die aktuelle Rolle des Testbenutzer nehmen. Für ein produktives System sollte man hierzu eine eigene Rolle definieren. Diese muss "manuell" sein, d.h. sollte keine automatischen Rechte erhalten. Man kann dann in dieser Rolle einzeln die Rechte für Prozesse etc. vergeben. Hierdurch ergibt sich nochmal eine zusätzliche Sicherheit zu der spezifischen Definition der WebServices über das bewährte iDempiere Rollensystem.


SoapUI

Nachdem wir nun die ersten Experimente mit Bordmitteln (sprich: mit dem Browser) durchgeführt haben, wird es Zeit, ein ordentliches Werkzeug einzuführen, um weitere SOAP-Experimente durchzuführen. Ich empfehle hierzu SoapUI. ICh habe dazu die Open Source Version des Tools von der Webseite heruntergeladen (die *.bin.tar.gz Datei, hier in Version 4.5.1 und 4.6.4) und installiert.

Mit SopaUI kann man nun ein neues Projekt anlegen und dort automatisch Requests für die angebotenen SOAP-Methoden erzeugen lassen. Das sind dann bereits vorausgefüllte XML-Dateien, mit denen man lernen kann, wie das Protokoll funktioniert.


Problem mit demo.idempiere.com Zertifikat

Zwischen der SoapUI-Version 4.5.1 und 4.6.4 hat sich eine bedeutsame Änderung ergeben: Die neuere Version akzeptiert keine SSL-Verbindung zu einem Server, der nicht den richtigen Namen in seinem Zertifikat stehen hat. (siehe z.B. hier). Das erlaubt leider keine Verbindung zu https://demo.idempiere.com. Alle meine Versuche, das Problem zu lösen, sind gescheitert, weil dort gar kein Name eingegeben ist (man diesen also auch nicht irgendwie faken könnte).

Versuchsweise habe ich folgendes gemacht, was aber auch nicht geholfen hat (vielleicht hilft es dem nächsten, der den Fehler findet)

 openssl s_client -showcerts -connect demo.idempiere.com:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >demo-idempiere-com-Certificate.pem
 keytool -import -keystore soapUI.keystore -alias demo.idempiere.com -file demo-idempiere-com-Certificate.pem -ext san=dns:demo.idempiere.com

Meine eigene Lösung war, einfach das ältere SoapUI zu nutzen.


wget

Natürlich kann man auch ohne SoapUI mit seinem WebServices-Server spielen. Für den oben eingerichteten Service auf dem Demo-Server kann man folgende Datei abspeichern.

 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:_0="http://idempiere.org/ADInterface/1_0">
  <soapenv:Header/>
  <soapenv:Body>
     <_0:queryData>
        <_0:ModelCRUDRequest>
           <_0:ModelCRUD>
              <_0:serviceType>GetContacts</_0:serviceType>
              <_0:Filter>phone IS NOT NULL</_0:Filter>
           </_0:ModelCRUD>
           <_0:ADLoginRequest>
              <_0:user>superuser @ idempiere.com</_0:user>
              <_0:pass>System</_0:pass>
              <_0:lang>en_US</_0:lang>
              <_0:ClientID>11</_0:ClientID>
              <_0:RoleID>102</_0:RoleID>
              <_0:OrgID>0</_0:OrgID>
              <_0:WarehouseID>0</_0:WarehouseID>
              <_0:stage>0</_0:stage>
           </_0:ADLoginRequest>
        </_0:ModelCRUDRequest>
     </_0:queryData>
  </soapenv:Body>
 </soapenv:Envelope>

Diese Datei habe ich übrigens mit SoapUI erzeugt und dann gespeichert. Man ruft dieses Beispiel auf mit

 wget https://demo.idempiere.com/ADInterface/services/ModelADService --no-check-certificate --post-file=demo-test.xml --output-document=data.xml

oder, wer es hübscher möchte:

 wget https://demo.idempiere.com/ADInterface/services/ModelADService --no-check-certificate --post-file=demo-test.xml --output-document=- -q | xmllint --format -

Die Option "no-check-certificate" ist der Konfiguration des demo-Servers geschuldet und sollte in einem Produktivsystem natürlich nicht nötig sein. Die Ausgabe kann dann z.B. so aussehen:

 <?xml version="1.0"?>
 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
     <ns1:queryDataResponse xmlns:ns1="http://idempiere.org/ADInterface/1_0">
       <WindowTabData xmlns="http://idempiere.org/ADInterface/1_0" NumRows="1" TotalRows="1" StartRow="1">
         <DataSet>
           <DataRow>
             <field column="Phone">
               <val>0123 45 67890</val>
             </field>
             <field column="Name">
               <val>Carl Boss</val>
             </field>
             <field column="AD_User_ID">
               <val>104</val>
             </field>
           </DataRow>
         </DataSet>
         <RowCount>1</RowCount>
         <Success>true</Success>
       </WindowTabData>
     </ns1:queryDataResponse>
   </soap:Body>
 </soap:Envelope>


Client-Programmierung in Java

Auswahl einer Bibliothek

Die Programmierung eines SOAP-Clients scheint gar nicht so trivial und gut dokumentiert zu sein, wie man bei der Bekanntheit des Buzzwords "SOAP" vermuten könnte. Das Java Web Services Tutorial über JAX-WS von Oracle ist gelinde gesagt sehr kompliziert. Ein zusätzliches Problem stellt sich, da ich gerne eine Lösung hätte, die auch unter Android funktioniert. Google hat jedoch keinerlei SOAP-Unterstützung in die Android-Kernklassen eingebaut (nach dem Studium der Lösung von Oracle habe ich da ein bisschen Verständnis für...). Im Grunde weiss ich nicht, ob etwas dagegen spricht, die Referenzimplementierung von JAX-WS einzeln herunterzuladen und zu benutzen.

Alle Links, die ich hierzu gefunden habe, benutzen allerdings die schlankere ksoap2-Bibliothek (github-Repository).

Andere Ansätze könnten Apache CXF benutzen, das z.B. von android-ws-client benutzt wird, um automatisch Java-Code zu erzeugen. Ich habe mir CXF aber bisher noch nicht näher angesehen. Eine kurze Recherche ergab Hinweise, es direkt unter Android nicht läuft, aber irgendwie in der Lage ist, Client-Code zu generieren, der dann läuft (das nutzt wohl android-ws-client).

Auch noch gefunden habe ich JiBX/WS. Dieser Artikel erklärt, wie es benutzt werden kann, um Client-Code zu generieren.


Problem mit ungültigem Zertifikat

Wie ich oben bereits andeutete, ist das Zertifikat, das der iDempiere-Demoserver zur Zeit benutzt, nicht nur selbstsigniert, sondern auch ohne Namen (CN). Ein solches Zertifikate-Problem gehört zwar eigentlich nicht zum Kern des hier beschriebenen SOAP-Themas, ich möchte aber dennoch einige Links zum Thema anbieten, falls jemand ein ähnliches Problem hat.

Auf der Android Developer Seite über Security with HTTPS and SSL gibt es ein schönes Beispiel, wie man ein Zertifikat von einem Server extrahiert und dieses dann in einem eigenen Keystore akzeptiert. Ich habe ein solches mit dem folgenden Befehl extrahiert, dann in das /res/raw/-Verzeichnis meines Android-Projektes kopiert und mit dem genannten Code dafür gesorgt, das es akzeptiert wird. (hier gibt es übrigens eine Erklärung zu den verschiedenen Formaten von Zertifikatsdateien.)

 openssl s_client -showcerts -connect demo.idempiere.com:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >idempierecert.pem

Leider nützt diese Methode nichts, weil die SSL-Implementierung standardmäßig nicht nur das Zertifikat prüft, sondern auch dessen Namen. Für diesen Fall gibt es aber auch eine Lösung: In dieser Stackoverflow-Frage versteckt sich eine relativ kurze Antwort, die jegliche Zertifikatsprüfung deaktiviert (die Antwort mit NullHostNameVerifier und NullX509TrustManager).