Webservices verwenden mit Oracle 10gR2 und UTL_DBWS

Auf Grund meiner Arbeit musste ich mich in letzter Zeit mit Service Oriented Architecture (SOA) und der Verarbeitung von Services beschäftigen. In den letzten Tagen hatte ich die Aufgabe mit einer Oracle 10gR2 Datenbank einen Webservice zu verwenden. Hörte sich für mich leicht an, irgendwie wurde das ganze doch ein wenig komplizierter 😉

Zunächst gab es ein paar Verzögerungen bei der Installation des UTL_DBWS Paketes, welches für die Interaktion zwischen Webservice und Datenbank nötig war. Nachdem das Paket dann endlich einsatzbereit war, machte ich mich daran die Dokumentation zu verstehen. Leider konnte ich keine Beispiele finden, die direkt von Oracle zur Verfügung gestellt wurden. Daher machte ich mich auf die Suche nach Foren und Blogs. Interessant war dabei, dass Funktionen zu existieren scheinen, die nicht in der Dokumentation vorhanden sind. Da ich kein komplett funktionierendes Beispiel finden konnte, verbrachte ich die meiste Zeit mit Ausprobieren und Fehlersuche…

Nach recht viel Frust läuft meine Funktion nun jedoch und ich werde sie hier kurz präsentieren und hoffe, dass sie anderen Programmierern bei ihrer Suche helfen kann 😉

Der Service

Neben dem eigentlichen Service, den ich verarbeiten sollte, machte ich mich nach einiger Zeit auf die Suche nach anderen die ich ebenfalls zum testen verwenden konnte. Dabei fand ich die Seite WebserviceX.NET welche recht viele Webservices bereitstellt. Das folgende Beispiel wird die GetWeather Funktion des GlobalWeather Services verwenden. Diesem Webservice werden Stadt und Land übergeben und er gibt die aktuelle Wettervorhersage zurück.

Das Beispiel

Mein Ansatz beschreibt eine Datenbankfunktion, welche Stadt und Land als Parameter übergeben bekommt und die Antwort des Webservices als Oracle Xmltype zurück gibt.  Meine Implementation dieser Funktion sieht wie folgt aus:

create or replace function getWeather
 (city in varchar2, country in varchar2)
 return xmltype
is
 dbws_service sys.utl_dbws.SERVICE;
 dbws_call sys.utl_dbws.CALL;

 dbws_wsdl_url varchar2(128);
 dbws_namespace varchar2(128);
 dbws_service_qname sys.utl_dbws.QNAME;
 dbws_port_qname sys.utl_dbws.QNAME;
 dbws_operation_qname sys.utl_dbws.QNAME;

 request sys.Xmltype;
 result sys.Xmltype;
begin
 dbws_wsdl_url := 'http://www.webservicex.net/globalweather.asmx?wsdl';
 dbws_namespace := 'http://www.webserviceX.NET';

 dbws_service_qname := sys.utl_dbws.to_QName(dbws_namespace, 'GlobalWeather');
 dbws_port_qname := sys.utl_dbws.to_QName(dbws_namespace, 'GlobalWeatherSoap');
 dbws_operation_qname := sys.utl_dbws.to_QName(dbws_namespace, 'GetWeather');

 dbws_service := sys.utl_dbws.create_service(
 service_Name => dbws_service_qname,
 wsdl_document_location => URIFACTORY.getUri(dbws_wsdl_url));

 dbws_call := sys.utl_dbws.create_call(
 service_Handle => dbws_service,
 port_name => dbws_port_qname,
 operation_name => dbws_operation_qname);

 sys.utl_dbws.set_property(dbws_call, 'SOAPACTION_USE', 'TRUE');
 sys.utl_dbws.set_property(dbws_call, 'SOAPACTION_URI', 'http://www.webserviceX.NET/GetWeather');
 sys.utl_dbws.set_property(dbws_call, 'OPERATION_STYLE', 'document');

 request := xmltype('<GetWeather xmlns="http://www.webserviceX.NET">
 <CityName>' || city || '</CityName>
 <CountryName>' || country || '</CountryName>
 </GetWeather>');

 result := sys.utl_dbws.invoke(dbws_call, request);

 sys.utl_dbws.release_call(call_Handle => dbws_call);
 sys.utl_dbws.release_service(service_Handle => dbws_service);

 return result;
end getWeather;

Zunächst einmal kann man sehen, dass einige Variablen definiert wurden. Diese werden später für die Konfiguration des Serviceaufrufs verwendet. Zuerst muss der Speicherort der WSDL Datei bekannt gegeben werden. Diese Datei enthält Informationen, die für den Aufruf des Services benötigt werden. Außerdem muss man den Service Namespace angeben, den man in der WSDL Datei im targetNamespace Attribut des wsdl:definitions Knotens finden kann.

dbws_wsdl_url := 'http://www.webservicex.net/globalweather.asmx?wsdl';
dbws_namespace := 'http://www.webserviceX.NET';

Die folgenden drei Variablen Beschreiben den Service, Port und die Operation die aufgerufen werden sollen. Die ersten beiden Werte findet man gegen ende der WSDL Datei in der wsdl:service Sektion. Diese Sektion definiert den Service und seine Ports. In meinem Fall habe ich den ersten Port verwendet. Hier der entsprechende Auszug aus der WSDL Datei (siehe unterstrichene Attribute):

<wsdl:service name="GlobalWeather">
  <wsdl:port name="GlobalWeatherSoap" binding="tns:GlobalWeatherSoap">
    <soap:address location="http://www.webservicex.net/globalweather.asmx"/>
  </wsdl:port>
  ...
</wsdl:service>

Die Definition der Funktion findet man in der WSDL Datei unter wsdl:PortType. Hier werden alle Funktionen die der jeweilige Port anbietet aufgelistet. Hier die Definition der Operation die dieses Beispiel verwendet (siehe unterstrichenes Attribut):

<wsdl:operation name="GetWeather">
  <wsdl:documentation>
    Get weather report for all major cities around the world.
  </wsdl:documentation>
  <wsdl:input message="tns:GetWeatherSoapIn"/>
  <wsdl:output message="tns:GetWeatherSoapOut"/>
</wsdl:operation>

Nachdem diese Vorkehrungen getroffen sind, müssen jeweils eine Service und Call Instanz unter Verwendung dieser Parameter erstellt werden. Hierfür muss man die Funktionen UTL_DBWS.create_service() und UTT_DBWS.create_call() verwenden:

dbws_service := sys.utl_dbws.create_service(
 service_Name => dbws_service_qname,
 wsdl_document_location => URIFACTORY.getUri(dbws_wsdl_url));

 dbws_call := sys.utl_dbws.create_call(
 service_Handle => dbws_service,
 port_name => dbws_port_qname,
 operation_name => dbws_operation_qname);

Als nächstes müssen noch ein paar Einstellungen vorgenommen werden. Die Optionen die für dieses Beispiel interessant waren, konnte ich in der wsdl:binding Sektion der WSDL Datei finden:

<wsdl:operation name="GetWeather">
  <soap:operation soapAction="http://www.webserviceX.NET/GetWeather" style="document"/>
  ...
</wsdl:operation>

Nachdem somit alle Einstellungen vorgenommen sind, fehlt nur noch die eigentliche Nachricht, die wir an den Webservice senden wollen. In meinem Beispiel habe ich sie direkt als Xmltype definiert. Als Vorlage habe ich die Body Sektion genommen, die ich in der Beschreibung der GetWeather Funktion gefunden habe:

request := xmltype('<GetWeather xmlns="http://www.webserviceX.NET">
 <CityName>' || city || '</CityName>
 <CountryName>' || country || '</CountryName>
 </GetWeather>');

Nun kann man den Service mit dieser Nachricht aufrufen:

result := sys.utl_dbws.invoke(dbws_call, request);

Ein Funktionsaufruf um das ganze zu testen kann wie folgt aussehen:

begin
 dbms_output.put_line(getWeather('Southampton', 'United Kingdom').getStringVal());
end;
/

Und liefert bei Erfolg etwa folgende Ausgabe:

SQL> begin
 2      dbms_output.put_line(getWeather('Southampton', 'United Kingdom').getStringVal());
 3  end;
 4  /

<GetWeatherResponse xmlns="http://www.webserviceX.NET">
 <GetWeatherResult>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;
&lt;CurrentWeather&gt;
 &lt;Location&gt;Southampton / Weather Centre, United Kingdom (EGHI) 50-54N 001-24W 0M&lt;/Location&gt;
 &lt;Time&gt;Feb 23, 2010 - 10:50 AM EST / 2010.02.23 1550 UTC&lt;/Time&gt;
 &lt;Wind&gt; from the E (100 degrees) at 8 MPH (7 KT) (direction variable):0&lt;/Wind&gt;
 &lt;Visibility&gt; greater than 7 mile(s):0&lt;/Visibility&gt;
 &lt;SkyConditions&gt; mostly cloudy&lt;/SkyConditions&gt;
 &lt;Temperature&gt; 39 F (4 C)&lt;/Temperature&gt;
 &lt;DewPoint&gt; 37 F (3 C)&lt;/DewPoint&gt;
 &lt;RelativeHumidity&gt; 93%&lt;/RelativeHumidity&gt;
 &lt;Pressure&gt; 29.26 in. Hg (0991 hPa)&lt;/Pressure&gt;
 &lt;Status&gt;Success&lt;/Status&gt;
&lt;/CurrentWeather&gt;</GetWeatherResult>
</GetWeatherResponse>

PL/SQL procedure successfully completed

Ich hoffe dieses Beispiel hilft anderen mehr als diejenigen, die ich gefunden habe 😉

One Reply to “Webservices verwenden mit Oracle 10gR2 und UTL_DBWS”

Leave a Reply