Ausgehende Webservices
Prüfung der Kompatibilität
Ob ein Webservice eingebunden werden kann oder nicht, hängt von äußeren Bedingungen ab, auf die CURSOR Software AG keinen Einfluss hat. Unter anderem müssen folgende Bedingungen erfüllt sein:
Die sogenannte WSDL-Datei muss zur Verfügung stehen. In der Regel gibt der Anbieter eine URL bekannt, über die CURSOR-CRM die WSDL-Datei abrufen kann.
Die innere Struktur der WSDL-Datei muss dem Standard WS-I Basic Profile 1.1 entsprechen und zur Parameterbeschreibung die Art document/literal nutzen. Ist die Datei nicht WS-I compliant kann CURSOR-CRM den Web Service nicht ansprechen.
Die Aussage des Web Service-Anbieters oder eine oberflächliche Sichtprüfung der WSDL-Datei genügen nicht. Das frei verfügbare Programm SoapUI kann die Einhaltung des WS-I Basic Profile-Standards überpüfen und einen Prüfbericht erzeugen. Hier wird nur formal geprüft, ob die WSDL gültig ist. Unstimmigkeiten bei den Aufruf- und Rückgabeparametern können weiterhin auftreten.
Werden Imports von Schema-Dateien innerhalb einer WSDL mit relativen Pfaden definiert, so kann es zu Problemen bei der Ausführung und dem Erstellen von Web Services führen. Hier ist eine manuelle Prüfung nötig, ob der Web Service mit dieser WSDL angelegt werden kann oder ob der eigentliche Service/Methode von dem Import-Anweisungen betroffen ist. Es könnte sein, dass der Service nicht vom Import abhängt. Generell ist der Import mit relativen Angaben ein Hindernis.
CURSOR-CRM unterstützt Basic Authentication und ab Version 12.2 WS-Security-Anmeldung unter Nutzung des Username-Token.
Der Applikationsserver oder Rich Client muss auf den Web Service zugreifen können. Dies geschieht in der Regel über die Protokolle http oder https. Dies kann an allgemeinen Beschränkungen des Internetzugriffs scheitern. Ggf. ist es notwendig, client- oder serverseitig relevante SSL-Zertifikate zu hinterlegen.
Die obig genannten Punkte sollten im Vorfeld geprüft werden, bevor die eigentliche Implementierung stattfindet.
Die Umgebung für den Aufruf des Webservices
Client oder Server
Der Aufruf des Webservices erfolgt entweder
im Client oder
im Applikationsserver, was der Standard ist.
Gesteuert wird dies über die Benutzereinstellungen oder die Systemeinstellungen durch die Einstellung
Integration/Web Services direkt in der Anwendung aufrufen
Wenn in den Benutzereinstellungen oder den Systemeinstellungen die Einstellung gesetzt ist, dann erfolgen die Aufrufe von Webservices im Client.
Proxy
Da der Aufruf eines Services über das Internet geht, ist es notwendig, den Proxy zu definieren, wenn ein solcher verwendet werden muss, um die Verbindung zum Internet aufzubauen. Je nachdem wo der Aufruf erfolgt - im Client oder im Server - ist die Definition des Proxys an einer anderen Stelle vorzunehmen.
Proxy bei Aufruf aus dem Client
Benutzereinstellungen/Netzwerk Einstellungen
Proxy bei Aufruf aus dem Server
Systemeinstellungen/Netzwerk Einstellungen Applikationsserver
Webservices in der Administrationskonsole
Der Eintrag Web Services in der Adminkonsole bietet Optionen zur Einrichtung, Wartung, Test, Ex- und Import eines Web Services. Die verfügbaren Optionen werden in den folgenden Kapiteln beschrieben.
Abbildung: Webservices in der Administrationskonsole
Anlegen eines neuen Webservices
Durch einen Klick auf den Schalter
Neu startet der Einrichtungswizard.
Abbildung: Auswahl der WSDL-Datei
Die Schreibweise der Service-Id wird auf Gültigkeit überprüft. Sie muss mit einem Großbuchstaben beginnen und darf als weitere Zeichen nur Buchstaben, Zahlen und Unterstriche enthalten und muss zudem eindeutig sein.
Die angegebene Adresse der WSDL-Datei muss zum Zeitpunkt der Neuanlage zugreifbar sein. Kann z. B. eine Internet-Adresse nicht aufgelöst werden, kann der Dialog nicht fortsetzt werden.
Sind beide Angaben gemacht, muss der Benutzer die Überprüfung durch einen Klick auf die Schaltfläche Verfügbarkeit testen starten. Hierbei anhand der oben angegebenen Kriterien entschieden, ob der Wizard fortgeführt werden kann. Ist dies der Fall, wird die Weiter-Schaltfläche des Wizard freigeschaltet.
Tipp
Bei der Erstellung kann entweder auf eine lokale Kopie oder auf eine Original-URL der WSDL verwiesen werden. Im ersten Fall wird die WSDL als Kopie in der Datenbank abgelegt und für alle weiteren Operation genutzt. Bei der Angabe der Original-URL wird diese bei späteren Aufrufen neu eingelesen. Wird der erste Ansatz genutzt und der Inhalt der WSDL ändert sich, so muss der Web Service erneut eingerichtet werden.
Der zweite Ansatz birgt den Vorteil, dass eventuelle Änderungen am Web Service sofort berücksichtigt werden. Innerhalb der Adminkonsole kann man diese Eigenschaft des Web Service hinter dem Feld 'Webservice-Adresse' einsehen, wobei 'DB' für eine lokale Kopie in der Datenbank steht, 'REMOTE' für die Angabe einer URL.
Abbildung: Auswahl der Funktion
Im nächsten Schritt kann der Webservice näher definiert werden. Dazu ist der Service auszuwählen, die damit verbundenen Verbindungstypen (Ports) und die gewünschte Funktion. Die Auswahlfelder werden automatisch gemäß den verfügbaren Optionen aktualisiert (siehe Abbildung: Auswahl der Funktion)
Durch Drücken des Schalters Fertig werden die Einstellungen des Webservices in der Übersicht angezeigt. Eventuelle Änderungen können hier vorgenommen werden, bevor der Webservice durch Speichern im System abgelegt wird.
Abbildung: Übersicht
Editierbares WSDL-Adressfeld innerhalb der Adminkonsole
Innerhalb der Adminkonsole konnte bis Version 11.2 nach einer Neuanlage bislang nur das eigentliche Skript des Aufrufes geändert werden. Zuvor angegebene Eigenschaften wie z. B. die Adresse der WSDL konnten nicht geändert werden. Änderte sich der Ort der WSDL, war man bislang gezwungen den Service zu exportieren, die Adresse zu ändern und anschließend zu importieren. Nun kann die Änderung direkt in der Adminkonsole vorgenommen werden.
Diese Funktionalität steht nur zur Verfügung, falls der Web Service-Type den Wert REMOTE hat.
Testen eines Webservices
Um einen erstellten Webservice zu testen, kann der Schalter Test verwendet werden.
Durch einen Klick auf
Test wird der Webservice ausgeführt und im Anschluss das Ergebnis in einem Dialog angezeigt.
Wichtig hierbei ist, dass dabei Binding-Parameter bei einem Test nicht ausgewertet werden, da wir uns währenddessen im Kontext eines Datensatzes befinden.
Die Binding-Paramter beziehen sich auf Werte, die aus der Maske kommen (Maskenfelder oder Maskenskipt-Variablen). Siehe unten bei "Aufrufskripte" und im nachfolgenden Text.
Ein Beispiel vorab:
binding.parameter.get("CITY")
bezieht sich auf folgende Maskenskriptzeile (die Maskenskript-Variable s_city enthält hier einen Ortsnamen):
parameterMap.put("CITY", s_city),
Danach folgt im Maskenskript der Webservice-Aufruf :
Object results = callWebService("Bedirect_search", null, parameterMap);
def zipCode0 = binding.parameter.get("...") //dies ist nicht möglich
def zipCode0 = 12345 //dies ist möglich
def result = proxy.GetWeatherByZipCode(zipCode0)
Parametergenerierung
Bei der Erstellung eines Webservice-Scriptes müssen die Methoden- und Parameter-Namen so angegeben werden, wie sie in der WSDL definiert wurden. Bei der Neuanlage eines Web Services wird, soweit wie möglich, ein Skriptgerüst generiert, um eine Hilfestellung bei der Einrichtung zu geben.
def accountNumber0 = binding.parameter.get("...") // <string>
def login1 = binding.parameter.get("...") // <string>
def mandator2 = binding.parameter.get("...") // <string>
def tid3 = binding.parameter.get("...") // <string>
def password4 = binding.parameter.get("...") // <string>
def bankCode5 = binding.parameter.get("...") // <string>
def result = proxy.accountCheck(accountNumber0, login1, mandator2, tid3, password4, bankCode5)
Alle Parameter des Methodenaufrufes werden in der Reihenfolge angegeben, wie sie in der Signatur definiert wurden. Zudem wird bei primitiven Typen ein Platzhalter eingesetzt, der indiziert um welchen Typ es sich konkret handelt (z. B. String, Float, etc.). Stellt der Parameter einen komplexen Typ dar, wird zusätzlich der Aufruf zur Objekterzeugung generiert.
def getWeatherByZipCode0 = proxy.create("net.webservicex.GetWeatherByZipCode") // Objekterzeugung
getWeatherByZipCode0.zipCode = binding.parameter.get("...") // <string>
def result = proxy.GetWeatherByZipCode(getWeatherByZipCode0)
Hinweis zur Überprüfung und Auflistung der Parameter
Um die Erstellung eines Skriptes zu vereinfachen werden die aus der WSDL generierten Java-Dateien in einem temporären Verzeichnis abgelegt sobald der Webservice mit aktivierter Benutzer- oder Systemoption In Anwendung ausführen gestartet wurde. Sie können als Hilfe für Datentypen und -Strukturen genutzt werden. Im Vorspann dieser Dateien werden auch die dazugehörigen Abschnitte aus der WSDL angegeben.
Temporäres Verzeichnis: C:\Dokumente und Einstellungen\<user>\CARMEN\<server>\tmp\jaxb\
Aufrufskripte
Um einen Webservice zu konfigurieren bzw. den Aufruf vorzubereiten, kann in der Admin-Konsole ein Skript geschrieben werden. Das Skript wird in der Skriptsprache Groovy verfasst, dessen Syntax sich stark an Java anlehnt.
Dabei werden der Vor- und Abspann (z. B. der Import, das Instantiieren der Variablen bzw. die Aufbereitung des Ergebnisses) des Skriptes automatisch eingefügt. Dabei wird auch das Proxy-Objekt initialisiert, über das die Konfiguration und der Aufruf realisiert wird.
Verfügbare Befehle des Proxy-Objektes
| Setzt die Anmeldedaten der HTTP-Basic Authentication explizit. Normalerweise werden die in der Konsole angegebenen Anmeldedaten implizit gesetzt. |
| Erstellt ein Objekt der angegebenen Klasse, die in der dazugehörigen WSDL beschrieben ist. Dabei ist darauf zu achten, das der voll qualifizierde Name (Package-Name + Klassenname) angegeben wird. |
| Der eigentliche Aufruf des Webservices. Der Methodenname entspricht demjenigen, der zuvor bei der Einrichtung ausgewählt wurde. Der Rückgabewert des Aufrufes variiert je nach Service. Um eine einheitliche Verarbeitung zu gewährleisten muss der Rückgabewert der Variable binding.result zugewiesen werden. |
nur clientseitig verfügbar) | Fügt dem SOAP-Request ein Header-Element hinzu. Dazu muss das Header-Objekt zuvor erstellt werden und zusätzlich dessen Name und Namespace angegeben werden.
CODE
Die Werte "elementNamespace" und "elementName" müssen der WSDL entnommen werden (siehe |
Vor dem eigentlichen Aufruf kann auf Werte der Maske oder eigens gesetzte Werte (die z. B. aus dem Maskenskript stammen) zugegriffen werden. Dazu wird ein Binding-Objekt bereitgestellt.
Verfügbare Felder des Binding-Objektes
| Interne Variable. |
| Zugriff auf die gesetzten Parameter. Normalerweise eine HashMap <String, Object>, die die Werte des AttributeContainers und/oder Werte der benutzerdefinierten HashMap enthält. |
| Zugriff auf die evtl. gesetzte Liste von AttributContainern. Zum Beispiel ein Ergebnis einer Suche. |
| Das Objekt, dem das Ergebnis zugewiesen werden muss, um eine spätere Verarbeitung zu gewährleisten. |
Beispiele
Innerhalb des Aufrufskriptes können, wie zuvor erwähnt, alle Groovy-Anweisungen genutzt werden.
// Auslesen eines Maskenwertes, wenn der Web Service aus einem Maskenskript aufgerufen wurde
def parameter = binding.parameter.get("Fieldname.Entity")
def parameter = getLookupKey(binding.parameter.get("Lookupfield.Entity"))
// Auslesen spezieller Werte, wenn beim Aufruf des Web Services eigene Daten übergeben wurden.
def parameter = binding.parameter.get("myValueKey")
Hilfsmethoden
In dem Aufrufskript werden Methoden angeboten, um spezielle Datenfelder der Maske einfacher auslesen zu können bzw. erweiterte Funktionalitäten bereitzustellen.
Methode | Beschreibung |
---|---|
| Konvertiert ein Datumsfeld der Maske in das passende Datumsformat eines Web Services |
| Liest den Schlüssel eines Nachschlagefeldes. |
| Liest die Beschreibung eines Nachschlagefeldes. |
| Liest den Pk eines Nachschlagefeldes. |
| Gibt Informationen werden im Log-Verzeichnis des Clients in einer seperaten Datei aus. Berücksichtigt das log-Level des internen Loggers. |
In Textform:
// Konfigurationsskript
// ####################
enableLoginViaCurrentUser(true) // Anmeldung am CURSOR Web Service mit dem aktuellen User
// Aufrufskript
// ############
// Zugriff auf Maskenfeld (Typ: Text)
def param01 = binding.parameter.get("FreeDate1.Activity")
// Zugriff auf Maskenfeld (Typ: Datum)
def param02 = timestampToXMLGregorianDate(binding.parameter.get("FreeDate1.Activity"));
// Zugriff auf Maskenfeld (Typ: Lookup-Schlüssel)
def param03 = getLookupKey(binding.parameter.get("Priority.Activity"));
// Zugriff auf Maskenfeld (Typ: Lookup-PK)
def param04 = getLookupPk(binding.parameter.get("Priority.Activity"));
// Zugriff auf Maskenfeld (Typ: Lookup-Beschreibung)
def param05 = getLookupDescription(binding.parameter.get("Priority.Activity"));
// Informationen loggen
error("Aufruf des Web Service mit Parameterwert [" + param05 + "]")
// Aufruf des Services
def result = proxy.webserviceMethode(param01, param02, ...)
Die Variablenbezeichnung "w" darf nicht benutzt werden, da sie intern genutzt wird.
Tipp
In den beiden Skripteditoren 'Konfigurations-Skript' und 'Aufruf-Skript' steht die Autovervollständigung von Methoden und Konstanten zur Verfügung. Das Nachschlagen nach Methoden und Konstanten erfolgt per Tastenkombination STRG+LEERTASTE.
Konfigurationsskript
In der Konfiguration eines Web Services gibt es die Möglichkeit, unabhängig des eigentlichen Aufruf-Skriptes eine Konfiguration anzugeben. In dem Feld 'Konfigurations-Skript' können analog zum Aufruf-Skript verschiedene Aufrufe angegeben werden. Um einen Web Service einzurichten bzw. weitere Aktionen (z. B. WSDL anzeigen) aufzurufen, müssen z. B. die globalen Proxy-Einstellungen in der Anwendung hinterlegt sein.
Methode | Ab | Bis | Beschreibung |
---|---|---|---|
| 12.2 | Setzt die Anmeldedaten bei Nutzung der HTTP-BASIC-Authentication. | |
| 10.1 | Werden CURSOR-eigene Web Services aufgerufen kann dieser Aufruf genutzt werden, der eine Anmeldung mit dem aktuellen Benutzer konfiguriert. | |
| 12.2 | Setzt das WS-Security Username-Token. | |
| 10.2 | 18.2 | Setzt die Host- und Portangabe des Proxys. Wird durch die Systemeinstellung überschrieben. |
| 10.2 | 18.2 | Setzt die Host- und Portangabe des Proxys und zusätzlich den Benutzer und das Password. Wird durch die Systemeinstellung überschrieben. |
| 11.1 | Versucht den Methodenaufruf im Stil wrapped durchzuführen, sodass der Aufrufparameter als Ganzes übergeben werden kann. | |
| 12.2 | Aktiviert das erweiterte Logging der SOAP-request- und -response-Messages. Dies ist nützlich um die generierten und empfangenen Daten einzusehen (z. B. bei einer SSL-Verschlüsselung).
|
Ergebnistypen
Im Folgenden einige Bespiele für Ergebnistypen aus einer WSDL und wie auf das Ergebnis per Maskenskript zugegriffen wird. Listen und Arrays werden in HashMaps umgewandelt, was bedeutet, es kann auf das erste Element mit get(0)
zugegriffen werden, auf das zweite mit get(1)
und so weiter. Gibt der Webservice lediglich einen einfachen Datentyp zurück wird dieser ebenfalls in eine HashMap verpackt und zwar unter dem Schlüssel result
. Er kann also in der Folge mit result.get("result")
ausgelesen werden.
Beispiel 1
In diesem WSDL-Ausschnitt wird ein Ergebnistyp GetWeatherByPlaceNameResponse
definiert. Er enthält einen komplexen Typ WeatherForecasts
der wiederrum eine Reihe von einfachen Werten, sowie ein Array von WeatherData
-Elementen enthält.
<s:element name="GetWeatherByPlaceNameResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="GetWeatherByPlaceNameResult" type="tns:WeatherForecasts" />
</s:sequence>
</s:complexType>
</s:element>
<s:complexType name="WeatherForecasts">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="Latitude" type="s:float" />
<s:element minOccurs="1" maxOccurs="1" name="Longitude" type="s:float" />
<s:element minOccurs="1" maxOccurs="1" name="AllocationFactor" type="s:float" />
<s:element minOccurs="0" maxOccurs="1" name="FipsCode" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="PlaceName" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="StateCode" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Status" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Details" type="tns:ArrayOfWeatherData" />
</s:sequence>
</s:complexType>
<s:complexType name="ArrayOfWeatherData">
<s:sequence>
<s:element minOccurs="0" maxOccurs="unbounded" name="WeatherData" type="tns:WeatherData" />
</s:sequence>
</s:complexType>
<s:complexType name="WeatherData">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="Day" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="WeatherImage" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="MaxTemperatureF" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="MinTemperatureF" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="MaxTemperatureC" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="MinTemperatureC" type="s:string" />
</s:sequence>
</s:complexType>
Das Ergebnis würde demzufolge beispielsweise folgende Struktur haben:
"details":[
"weatherData":[
0:["weatherImage":"http://forecast.weather.gov/images/wtf/nfg.jpg", "maxTemperatureF":"56", "minTemperatureF":"43", "day":"Wednesday, November 19, 2008", "maxTemperatureC":"13", "minTemperatureC":"6"],
1:["weatherImage":"http://forecast.weather.gov/images/wtf/ra90.jpg", "maxTemperatureF":"52", "minTemperatureF":"47", "day":"Thursday, November 20, 2008", "maxTemperatureC":"11", "minTemperatureC":"8"],
2:["weatherImage":"http://forecast.weather.gov/images/wtf/ra50.jpg", "maxTemperatureF":"52", "minTemperatureF":"42", "day":"Friday, November 21, 2008", "maxTemperatureC":"11", "minTemperatureC":"6"]
]
],
"status":null, "fipsCode":"53", "allocationFactor":0.001192, "stateCode":"WA", "placeName":"SEATTLE", "longitude":122.33046, "latitude":47.611435
Um auf latitude
zuzugreifen, muss result.get("latitude")
aufgerufen werden, um den Wert 47.611435
zu bekommen. result.get("details").get("weatherData").get(0).get("maxTemperatureF")
würde 56
zurückgeben.
Beispiel 2
Diese WSDL-Datei definiert einen Rückgabetyp GetSupplierByCityResponse
der ein boolean GetSupplierByCityResult
und eine Liste SupplierDataLists
enthält.
<s:element name="GetSupplierByCityResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="GetSupplierByCityResult" type="s:boolean" />
<s:element minOccurs="1" maxOccurs="1" name="SupplierDataLists" type="tns:SupplierDataList" />
</s:sequence>
</s:complexType>
</s:element>
<s:complexType name="SupplierDataList">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="SupplierDatas" type="tns:ArrayOfSupplierData" />
<s:element minOccurs="1" maxOccurs="1" name="TotalRecords" type="s:int" />
</s:sequence>
</s:complexType>
<s:complexType name="ArrayOfSupplierData">
<s:sequence>
<s:element minOccurs="0" maxOccurs="unbounded" name="SupplierData" type="tns:SupplierData" />
</s:sequence>
</s:complexType>
<s:complexType name="SupplierData">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="SupplierNumber" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="CompanyName" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Address1" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Address2" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="City" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="State" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Zip" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="ZipPlus4" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Telephone" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="Description" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="IsSupplierParticipating" type="s:string" />
</s:sequence>
</s:complexType>
Diese Struktur würde ein Aufruf ergeben:
0=true,
1={
supplierDatas={
supplierData={
0={zipPlus4=10119, zip=NY, isSupplierParticipating=04, description=(212)760-1242, state=null, address1=250 W 34TH ST, address2=, companyName=KMART CORP, supplierNumber=0127051883, telephone=0002, city=NEW YORK},
1={zipPlus4=10010, zip=NY, isSupplierParticipating=05, description=(212)982-7850, state=null, address1=6 E 23RD ST, address2=, companyName=PEARLE VISION INC, supplierNumber=0132600348, telephone=4401, city=NEW YORK},
2={zipPlus4=10019, zip=NY, isSupplierParticipating=05, description=(212)245-0686, state=null, address1=732 9TH AVE, address2=, companyName=WESTWAY VISION INC, supplierNumber=0133130001, telephone=7317, city=NEW YORK}
}
}
}
Mit result.get(0)
kann GetSupplierByCityResult
erfragt werden, in diesem Fall also true
. get(1).get("supplierDatas").get("supplierData").get(1).get("description")"
liefert (212)982-7850
.
Fehler-Codes
Wenn ein Fehler während der Bearbeitung auftritt, kann dieser nach folgenden Typen unterschieden werden:
CONFIG | Fehler beim Laden der Web Service Configuration, z.B. falsche ID |
SCRIPT | Ungültiges WebService Skript |
FAULT | Eine SOAP Fault Nachricht, d.h. der WebService meldet eine Ausnahme |
AUTH | Die Authentifizierung funktioniert nicht bzw. ist fehlerhaft konfiguriert |
SSL | Die SSL-Konfiguration ist fehlerhaft |
REMOTE | Es konnte keine Verbindung zum WebService aufgebaut werden |
NONE | Ein nicht näher spezifizierter Fehler |
Die genaue Fehlerursache wird ins Client- bzw. Server-Log geschrieben.
Aufruf-Beispiele
Beispiel 1
Als erstes Beispiel soll ein einfachter Service-Aufruf installiert werden, der zu einer Bankleitzahl die Bank ermittelt. Die WSDL dafür lautet:
http://www.thomas-bayer.com/axis2/services/BLZService?wsdl
Für den ersten Versuch soll der Service über den Button Test aufgerufen werden. Dabei ist wie folgt vorzugehen:
Start der Admin-Konsole - Menüpunkt Sonstige Bereiche/Webservices
Neuen Webservice Aufruf anlegen über den Button Neu
Als ID beliebigen eindeutigen Namen eingeben, der mit einem Großbuchstaben beginnt. zB GetBank
URL der WSDL Datei angeben
Button Verfügbarkeit testen drücken. Wenn der Webservice erreichbar ist, wird der Button Weiter aktiv. Bei Problemen die Schreibweise der URL durch Eingabe im Browser testen. Dann müsste die WSDL Definition ausgegeben werden. Ansonsten prüfen, ob der Proxy in den Benutzereinstellungen korrekt gesetzt ist.
Button Weiter drücken
Servicename BLZService, Portname BLZServiceSOAP12port_http, Methodenname getBank wählen
Button Fertig anklicken. Die Daten werden in das Hauptformular übernommen und es wird ein Aufruf-Script generiert.
Wenn ein Proxy zum Zugriff auf das Internet verwendet wird, dann muss dieser auch im Konfigurations-Skript definiert werden: setProxySettings("proxy", "8080").
Das generierte Aufruf-Script ist zum einen falsch und zum anderen funktionert der Aufruf von binding.parameter.get() beim Drücken des Test-Buttons nicht. Es wird deshalb geändert, so dass der Parameter als Konstante mitgegeben wird:def result = proxy.getBank("50010517")
Den Aufruf des Web-Services durch Drücken des Buttons Speichern speichern
Den Button Test drücken. Als Ergebnis sollte jetzt {bezeichnung=ING-DiBa, ort=Frankfurt am Main, plz=60628, bic=INGDDEFFXXX} - die zur BLZ 50010517 passende Bank ausgegeben werden
Beispiel 2
Nach oben beschriebenem Vorgehen wird ein WebService RiskCode angelegt, dessen Methode GetLloydsRiskCodeDetailByRiskCode
aufgerufen werden soll. Sollten Benutzername und Passwort erforderlich sein, können diese in der Maske eingegeben oder im Skript mit setBasicAuthentication(String username, String password)
nachgetragen werden.
Abbildung: Konfiguration des Webservice RiskCode
Das passende Skript könnte beispielsweise so aussehen:
def RiskCode = "3"
def result = proxy.GetLloydsRiskCodeDetailByRiskCode(RiskCode)
Dabei ist vorgeschrieben, dass das Ergebnis in einer Variable result
gespeichert wird, da diese intern später weiterverwendet wird.
Das Objekt proxy
wird zur Verfügung gestellt und die gewünschte Methoden müssen darauf aufgerufen werden. Dieses Objekt wird intern durch ergänzen des Codes durch folgende Zeile bereitgestellt:
def proxy = new GroovyWSClient("http://www.webservicex.net:85/LloydsRiskCodeService.asmx?wsdl", binding.classloader)
Die hier gewählte Methode GetLloydsRiskCodeDetailByRiskCode
erwartet einen String-Parameter RiskCode
, der hier auf 3
gesetzt wird. Das vom Methodenaufruf zurückgegebene Ergebnis wird in diesem Fall in result
geschrieben. binding
ist wie proxy
ein bereitgestelltes Objekt. In binding.result
muss das Ergebnis geschrieben werden, das später verwendet werden soll. Die Vereinheitlichung des Ergebnisses wird intern durch folgenden Aufruf erreicht:
WebServiceHelper w = new WebServiceHelper()
binding.result = w.flattenResult(result)
Damit sieht das tatsächlich ausgeführte Skript folgendermaßen aus:
def proxy = new GroovyWSClient("http://www.webservicex.net:85/LloydsRiskCodeService.asmx?wsdl", binding.classloader)
def RiskCode = "3"
result = proxy.GetLloydsRiskCodeDetailByRiskCode(RiskCode)
WebServiceHelper w = new WebServiceHelper()
binding.result = w.flattenResult(result)
Wie im folgenden WSDL-Ausschnitt dieses Webservices zu sehen, wird der komplexe Typ GetLloydsRiskCodeDetailByRiskCodeResponse
zurück gegeben, der in diesem Fall in result
steht. Dieser komplexe Typ enthält den weiteren komplexen Typ ArrayOfRiskCode
, auf den mit result.get("riskCode")
zugegriffen werden kann. Dieses ArrayOfRiskCode
-Objekt wiederrum ist ein Array dessen RiskCode
-Elemente mit get(0) ... get(x)
erhalten werden können. Diese RiskCode
-Elemente schließlich enthalten String-Objekte mit Werten. Diese String-Objekte werden mit get("IhrName")
ausgelesen.
In obigem Maskenskript-Beispiel wird also zunächst das GetLloydsRiskCodeDetailByRiskCodeResponse
-Objekt ausgegeben, anschließend das erste RiskCode
-Element aus dem Array genommen und sein Wert firstYearOfAccount
ausgegeben, sowie sein Wert riskCodeDescription
in das Maskenfeld Freitext 1
geschrieben.
<s:element name="GetLloydsRiskCodeDetailByRiskCode">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="RiskCode" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetLloydsRiskCodeDetailByRiskCodeResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetLloydsRiskCodeDetailByRiskCodeResult" type="tns:ArrayOfRiskCode" />
</s:sequence>
</s:complexType>
</s:element>
<s:complexType name="ArrayOfRiskCode">
<s:sequence>
<s:element minOccurs="0" maxOccurs="unbounded" name="RiskCode" nillable="true" type="tns:RiskCode" />
</s:sequence>
</s:complexType>
<s:complexType name="RiskCode">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="LloydsRiskCode" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="RiskType" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="MarketCode" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="NewTerrorismCode" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="LastYearOfAccount" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="FirstYearOfAccount" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="RiskCodeDescription" type="s:string" />
</s:sequence>
</s:complexType>
Beispiel 3
Ein weiterer Webservice "WeatherForecast
" wird mit folgender Konfiguration eingerichtet:
"GetWeatherByPlaceName
" erwartet einen String, der den Namen einer Stadt enthält. In diesem Beispiel soll diese Stadt aus dem Textfeld "Freitext 1
" der Aktivität ausgelesen werden. Dazu ist folgender Skript-Code nötig:
def PlaceName = binding.parameter.get("FreeText1.Activity")
def result = proxy.GetWeatherByPlaceName(PlaceName)
In "binding.parameter" sind alle Felder der Maske enthalten, aus der der Webservice aufgerufen wurde. Mit "get("derNameDesFeldes")" kann damit der Inhalt des gewünschten Feldes erfragt werden. Hier wird dieser Inhalt in "PlaceName" gespeichert und an die Methode "GetWeatherByPlaceName" übergeben.
Mit diesem Maskenskript würde der soeben erstellte Webservice aufgerufen:
IWebServiceResult result = callWebService("WeatherForecast");
alert(result);
Beispiel - Suchen im CURSOR-CRM
Die WSDL-Datei des JBoss kann unter folgender URL gefunden werden
http://localhost:18080/CARMEN-ejb/WebServiceController?wsdl
Oder die Datei kann auch im Dateisystem geladen werden
..\JBoss\server\default\data\wsdl\CARMEN.ear\CARMEN-ejb.jar\WebServiceController.wsdl
Für dieses Beispiel wird die Funktion search aufgerufen und dafür die XML-Struktur einer Suche übergeben.
Die eigentliche Suche bzw. eine Vorlage kann aus der Anwendung exportiert werden.
Die Suche wird mit den Rechten des für den Webservice angegebenen Benutzers ausgeführt. Das Kürzel ist in Großbuchstaben anzugeben, als Passwort benötigt man den Hashwert aus der Datenbank.
Im Groovy-Skript ist die XML-Suche als Parameter zu übergeben.
def xmlSearch = """<CreateComplexSearch> <ExtendedSearch PK="" PlainKey="Search" Description="Search" UserPrivateSearch="false" QuickSearch="false" UseCost="true" IgnoreCase="true" SubmaskOnly="false" SystemSearch="false" CursorStandard="false" UseDistinct="false" TopCount="-1"> <Query> <SubQuery operator="de.cursor.jevi.common.search.Operator\$AndOperator" relationName="" entityName="Customer" subQueryInSearchResult="true" useExists="false" useOuterJoin="false" optionalQuery="false" breakQuery="false" hint=""> <Condition searchResultField="true" listKey="false" searchField="false" deleteable="false" readOnly="false" defaultSearchValue="false"> <AttributeName>MatchCode.Customer</AttributeName> <Function functionClassName="de.cursor.jevi.common.search.function.NoConditionFunction" numberOfParameters="0"> <FunctionProperty functionPropertyName="TableName" functionPropertyDataType="java.lang.String" functionPropertyValue="" /> </Function> </Condition> <Condition searchResultField="false" listKey="false" searchField="false" deleteable="false" readOnly="false" defaultSearchValue="false"> <AttributeName>Active.Customer</AttributeName> <Function functionClassName="de.cursor.jevi.common.search.function.EqualFunction" numberOfParameters="1"> <Parameter parameterClassName="java.lang.Boolean" parameterValue="true" /> </Function> </Condition> </SubQuery> </Query> </ExtendedSearch> </CreateComplexSearch>"""
Das $-Zeichen ist ein geschütztes Zeichen in der Groovy-Scriptsprache. Dieses muss mit \$
ersetzt werden. Ein Parameter über mehrere Zeilen mit 3 Anführungszeichen angegeben werden.
Zum Aufruf muss auf dem Proxy die Methode search
aufgerufen werden. Das Ergebnis wird der definierten Variable result
zugewiesen.
def result = proxy.search(xmlSearch)
Welchen Parameter die Methode benötigt und von welchem Typ der Rückgabewert ist, kann aus der WSDL-Datei ausgelesen werden.
...
<message name="search">
<part name="xmlSearch" type="xsd:string"></part>
</message>
<message name="searchResponse">
<part name="result" type="xsd:string"></part>
</message>
Zum Aufruf des Webservices kann folgendes Maskenskript verwendet werden:
String result = callWebService("Search").get("result");
In unserem Beispiel hat das Ergebnis folgende Struktur und Inhalt:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Entries testRun="false">
<Entry entity="Customer" createIfNotFound="false">
<Field name="MatchCode.Customer" value="CURSOR GIESSEN" />
</Entry>
<Entry entity="Customer" createIfNotFound="false">
<Field name="MatchCode.Customer" value="CURSOR ENTWICKLUNG" />
</Entry>
</Entries>
Der Aufbau der XML-Import-Struktur wird im Kapitel XML Import-Schnittstelle genauer erläutert.
Import und Export eines Webservices
Über die Schalter Importieren und Exportieren kann eine Webservice-Konfiguration als externe Datei geladen bzw. gespeichert werden.
Export
Nach dem Öffnen einer Konfiguration zeigt die Exportfunktion den Inhalt der abzuspeichernden Datei an und ermöglicht das Auswählen eines Speicherortes (siehe Abbildung: Export).
Abbildung: Export
Import
Die Importfunktion öffnet einen Dialog zur Dateiauswahl. Nach Auswahl der Datei wird die geladene Konfiguration in der Übersicht angezeigt und kann bearbeitet bzw. gespeichert werden.
Feldmapping
Es besteht die Möglichkeit, dass ein Web Service mit verschiedenen Entitäten genutzt werden kann. Um dies zu ermöglichen, müssen die Felder im Web Service-Skript auf die gleiche Weise referenziert werden. Da jedoch die einzelnen Entitäten verschiedene Feldnamen haben, muss ein gemeinsamer Zugriff auf die jeweiligen Felder realisiert werden. Dieser Vorgang kann durch das 'Feldmapping' umgesetzt werden.
Definition des Mappings
In der Aktionsleiste der Web Service-Adminkonsole befindet sich der Punkt Mapping, der einen weiteren Editor öffnet.
In diesem Editor kann für verschiedene Felder eine gemeinsamer Variablenname angegeben werden. Über die Schaltfläche Neue Entiät werden alle Enitäten festgelegt, die diesen Web Service nutzen sollen. Im Anschluss wird das Synonym, also der gemeinsame Variablenname, für alle Entitäten festgelegt, indem die Schaltfläche Neues Synonym gedrückt wird. Nach der Benennung des Synonyms kann der jeweilige Feldname in der Spalte der Entität über eine Auswahlbox festgelegt werden. Angelegte Synonyme können wieder entfernt werden. Durch einen Klick auf Synonym entfernen wird die momentan markierte Zeile gelöscht. In gleicher Weise wird die momentan markierte Spalte durch einen Klick auf Entität entfernen gelöscht. Die erste Spalte mit den Synonymen kann nicht gelöscht werden. Sind keine Zeilen vorhanden wird beim Klick auf Entität entfernen die Spalte, die momentan als sortierende Spalte ausgewählt ist, gelöscht. Durch einen Klick auf Abbrechen, das Drücken von ESC oder das Klicken des Schließen-Knopfes am rechten oberen Dialogrand wird das ausgewählte Mapping verworfen. OK setzt das Mapping. Gespeichert ist es jedoch erst nach dem speichern des Webservice-Dialogs. Es muss für jede Entität für jedes Synonym ein Attribut ausgewählt werden, passiert dies nicht kann der Dialog nicht mit OK bestätigt werden.
Nutzung im Web Service-Script
Anstelle des Zugriffes über binding.parameter.get("FreeText14.Activty")
kann nun über binding.parameter.get("accountNumber")
auf den Feldwert der Aktivitäten- bzw. der Ansprechpartnermaske zugegriffen werden.
Das folgende Beispiel zeigt die Nutzung des selben Skriptes für mehrere Entitäten.
def login1 = <string>
def mandator2 = <string>
def password4 = <string>
def tid3 = <string>
// accountNumber = "FreeText14.Activity" oder "Freenumber4.ContactPerson"
def accountNumber0 = binding.parameter.get("accountNumber")
// bankCode = "FreeText15.Activity" oder "Freenumber5.ContactPerson"
def bankCode5 = binding.parameter.get("bankCode")
def result = proxy.accountCheck(accountNumber0, login1, mandator2, tid3, password4, bankCode5)
Webservices im Maskenscripting
Das Maskenskripting bietet die Möglichkeit einen Webservice aufzurufen und dessen Rückgabe zu verarbeiten. Dazu muss im Maskenskript die Methode callWebService("webservice-id", [List<AttributeContainer>, HashMap<String, Object>]*)
aufgerufen werden.
Aufrufparameter
"webservice-id" | Die Service-ID des Webservices |
---|---|
List<AttributeContainer> | Optional: Eine Liste von AttributeContainern, z. B. aus einer Suche. |
HashMap<String, Object> | Optional: Eine HashMap, die eigene Werte übergibt bzw. Werte des Standard-AttributeContainers überschreibt. |
Im Fehlerfall ist das Ergebnis nicht vom Typ IWebServiceResult, sondern vom Typ WebServiceExceptionType
. Die genaue Aufstellung der verschieden Fehler-Arten finden Sie im Kapitel Fehler-Codes.
Webservices durch einen Externen Aufruf
Einrichtung des Externen Aufrufes
Bei dem Erstellen eines Externen Aufrufes ist als ID die WebserviceID anzugeben. Sie entspricht der ID der Webservice-Neuanlage. Zusätzlich muss der Aufruftyp Webservice angegeben werden. Es erfolgt lediglich ein asynchroner Aufruf, dessen Rückgabewert nicht ausgewertet wird.
In dem Parameterfeld des Externen Aufrufes wird der benötigte Parameter WEBSERVICEID
eingetragen. Der Wert dieser Variable bestimmt sich aus der Service-ID des in der Adminkonsole eingerichteten Web-Services.
Das folgende Beispiel zeigt einen Beispielaufruf eines BLZ-Services:
WEBSERVICEID = weatherPlace
Die eigentliche Konfiguration des Webservices wird durch das Groovy-Skript in der Admin-Konsole bestimmt, dem der AttributeContainer zur Verfügung steht.
Sobald eine Aktivität geöffnet ist, wird der zuvor eingerichtete Externe Aufruf angeboten.
FAQ
Fehlermeldungen und mögliche Fehlerursache
Fehlermeldung | Ursache | Problembehebung |
---|---|---|
The Element "HTML" does not contain a namespace. | Die Gegenstelle liefert als Ergebnis eine Meldung, die in ungültigem XHTML verfasst ist - meist eine Meldung des Proxys. | Prüfen Sie die Proxyeinstellungen auf ihre Gültigkeit. |
Absturz des Applikationsserver beim Drücken des Test-Buttons mit der Exception: ERROR [de.cursor.jevi.server.web.WebServiceController] : de.cursor.exception.WebServiceException : java.net.ConnectException: connection refused: connect | Kein Zugriff auf den Webservice. Wahrscheinlich wurde der Proxy nicht definiert. | Prüfen Sie die Proxyeinstellungen in den Systemeinstellungen. Enthält das Konfigurations-Skript den Aufruf setProxySettings()? |
Antwort beim Drücken des Test-Buttons: null - org.jboss.remoting.serialization.ClassLoaderUtility.loadClass(ClassLoaderUtility.java:103) | Kein Zugriff auf den Webservice. Wahrscheinlich wurde der Proxy nicht definiert. | Prüfen Sie die Proxyeinstellungen in den Systemeinstellungen. Enthält das Konfigurations-Skript den Aufruf setProxySettings()? |
Antwort beim Drücken des Test-Buttons: java.net.ConnectException: Connection refused: connect | Kein Zugriff auf den Webservice. Wahrscheinlich wurde der Proxy nicht definiert. | Prüfen Sie die Proxyeinstellungen in den Benutzereinstellungen. Enthält das Konfigurations-Skript den Aufruf setProxySettings()? |
Antwort beim Drücken des Test-Buttons: javax.script.ScriptException: groovyx.net.ws.exceptions.InvokeException: org.apache.cxf.binding.soap.SoapFault: Error writing to XMLStreamWriter. groovyx.net.ws.exceptions.InvokeException: org.apache.cxf.binding.soap.SoapFault: Error writing to XMLStreamWriter. | Kein Zugriff auf den Webservice. Wahrscheinlich wurde der Proxy nicht definiert. | Prüfen Sie die Proxyeinstellungen in den Benutzereinstellungen. Enthält das Konfigurations-Skript den Aufruf setProxySettings()? |
| Eine benötigte Bibliothek wurde nicht geladen. Falls der Applikationsserver als Dienst eingerichtet ist, fehlt ein Eintrag in der Datei Wrapper.conf. | Fügen Sie die Zeile wrapper.java.additional.<#>=-Djava.endorsed.dirs=<JBoss-Pfad>\lib\endorsed hinzu und starten Sie den Dienst neu. |
Unable to create JAXBContext for generated packages: "net.java.dev.jaxb.array" doesnt contain ObjectFactory.class or jaxb.index | Die generierten Parameter-Klassen können nicht geladen werden. | Bitte prüfen Sie die Log-Ausgaben, ob sie eine Fehlermeldung des Compilers enthalten. Das temporäre Benutzer-Verzeichnis scheint nicht zugänglich zu sein. |
org.apache.cxf.interceptor.Fault: Found element {xxx}getxxxReturn but could not find matching RPC/Literal part | Die Definition der WSDL ist nicht WS-I compliant (RPC/encoded, Document/encoded anstelle von RPC/literal oder Document/literal). | Die WSDL muss gegen eine nach dem WS-I Web Service-Standard gültige Version ersetzt werden. |
Cannot cast object 'abc' with class 'java.lang.String' to class 'javax.xml.bind.JAXBElement' | Die Definition dieses Elementes hat das Attribute notNillable="true". |
oder
|
org.apache.cxf.interceptor.Fault: Marshalling Error: Instance of "com.example.Class01" is substituting "com.example.Class02", but "com.example.Class01" is bound to an anonymous type. oder Part {urn:com.example}request should be of type com.example.Class01Request, not com.example.Class01 | Der Aufruf der Web Service-Methode nutzt den Stil "unwrapped", d. h. die Parameter werden einzeln erwartet. |
Bsp: Aus def result = proxy.getMyValue(parameter01) wird def result = proxy.getMyValue(parameter01.member01, parameter01.member02).
|
javax.script.ScriptException: groovyx.net.ws.exceptions.InvokeException: java.lang.RuntimeException: Can't find input stream in message groovyx.net.ws.exceptions.InvokeException: java.lang.RuntimeException: Can't find input stream in message | Die Anmeldedaten können falsch sein. | Korrigieren Sie die Anmeldedaten. |
<body><h1>HTTP Status 401 - </h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u></u></p><p><b>description</b> <u>This request requires HTTP authentication ().</u></p><HR size="1" noshade="noshade"><h3>JBoss Web/3.0.0-CR2</h3></body> | Die Anmeldedaten können falsch sein. | Korrigieren Sie die Anmeldedaten. |
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source) | Der Web Service wird über https aufgerufen und die Zertifizierungsstelle des Zertifikates ist Java nicht bekannt. | Hinterlegen Sie das Zertifikat inkl. gültiger Verifizierungskette (Chain of Trust) im Keystore der Java Runtime des Clients bzw. im Keystore des Servers. |