Datenbanken, Internet Technologien - von am Donnerstag, November 19, 2009 9:24 - 0 Kommentare

Oracle WebServices im praktischen Einsatz

1. Einleitung

Dieser Praxisreport schildert den Einsatz von Oracle als WebService-Consument und WebService-Provider. Dabei wird anhand eines Praxisbeispiels gezeigt wie der Zugriff auf einen externen WebService aus einer Oracle-Datenbank mittels PL/SQL-Routine möglich ist um damit einen Ladeprozess (ETL) zu implementieren. Des weiteren wird gezeigt, wie die Datenbank ihre PL/SQL-Routinen als WebService zur Verfügung stellen kann.

2. Einführung in WebServices

Grds. stellt ein WebService einen Dienst (Service) dar, der über eine eindeutige URI (Uniform Resource Identifier) identifizierbar ist. Dabei spielt es für den Aufrufenden des WebServices bzw. dessen Anbieter keine Rolle, mit welcher Programmiersprache oder auf welchem Betriebssystem, das jeweilige andere System arbeitet. Aufrufender und Anbieter können dabei zusätzlich räumlich getrennt sein.

WebServices setzen dabei auf bewährte Internet-Technologien und Protokollen wie beispielsweise das HTTP-Protokoll auf. Dies hat zur Folge, dass für die Nutzung und die Bereitstellung von WebServices beispielsweise der Standardport 80 des HTTP-Protokolls genutzt werden kann und somit keine weitere Konfigurationen und Freischaltungen innerhalb der Unternehmens-Firewall notwendig sind.

In Zusammenhang mit WebServices wird häufig der Begriff „SOA“ genannt, welches aber nicht dasselbe meint. SOA als ServiceOrientierte Architektur bezeichnet lediglich ein Konzept wie eine Architektur service-orientiert aufgebaut sein kann, beschriebt jedoch keine Technologie oder gar eine Spezifikation.

Grundsätzlich senden Client-Programme (wie beispielsweise eine PL/SQL-Routine) Anfragen an einen WebService. Dieser sendet die Information dann an das aufrufende System zurück. Sowohl die Anfrage als auch die Antwort erfolgen dabei grundsätzlich im XML-Format. Dabei wird zwischen einer sog. „Header-Information“ und einer „Payload-Information“ unterschieden. Im Header werden dabei Steuerungsinformation versandt, während der Payload die eigentlichen Nutzdaten enthält.

Der Anbieter eines WebServices stellt für die Nutzung seines Systems aufrufbare Methoden zur Verfügung. Diese werden vom Anbieter in Form einer sog. „WSDL“ veröffentlich. WSDL (WebService Definition Language) ist eine Beschreibungssprache für Dienste zum Austausch von Informationen auf Basis von XML. Hierdurch enthält das aufrufende System Informationen darüber, welche Methoden und Operationen ein WebService wie bereitstellt, welche Parameter diese ggf. benötigen und wie etwaige Rückgaben aufgebaut sind.

Nachfolgend ein Beispiel für die WSDL einer öffentlich zugänglichen Wetter-Abfrage-Information:

http://www.webservicex.net/globalweather.asmx?WSDL

<!--?xml version="1.0" encoding="utf-8"?-->
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.webserviceX.NET" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetnamespace="http://www.webserviceX.NET" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<wsdl:types>
<s:schema elementformdefault="qualified" targetnamespace="http://www.webserviceX.NET">
<s:element name="GetWeather">
<s:complextype>
<s:sequence>
<s:element minoccurs="0" maxoccurs="1" name="CityName" type="s:string">
<s:element minoccurs="0" maxoccurs="1" name="CountryName" type="s:string">
</s:element></s:element></s:sequence>
</s:complextype>
</s:element>
<s:element name="GetWeatherResponse">
<s:complextype>
<s:sequence>
<s:element minoccurs="0" maxoccurs="1" name="GetWeatherResult" type="s:string">
</s:element></s:sequence>
</s:complextype>
</s:element></s:schema></wsdl:types></wsdl:definitions>

[...]

<wsdl:message name="GetWeatherSoapIn">
<wsdl:part name="parameters" element="tns:GetWeather">
</wsdl:part></wsdl:message>
<wsdl:message name="GetWeatherSoapOut">
<wsdl:part name="parameters" element="tns:GetWeatherResponse">
</wsdl:part></wsdl:message>

[...]

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

[...]

<wsdl:binding name="GlobalWeatherSoap" type="tns:GlobalWeatherSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http">
<wsdl:operation name="GetWeather">
<soap:operation soapaction="http://www.webserviceX.NET/GetWeather" style="document">
<wsdl:input>
<soap:body use="literal">
</soap:body></wsdl:input>
<wsdl:output>
<soap:body use="literal">
</soap:body></wsdl:output>
</soap:operation></wsdl:operation>

Dieser WebService stellt die Methode GetWeather zur Verfügung und erwartet dabei als obligatorische Parameter ein Stadtnamen (CityName) sowie ein Namen des Landes (CountryName) als String. Als Rückgabewert (GetWeatherRepsonse) wird ebenfalls ein String zurückgegeben. Auch die für den Konsumenten aufzurufende URL für den WebService wird übermittelt (http://www.webserviceX.NET/GetWeather). Jeder Programmierer kann nun diesen WebService nutzen und erhält dafür durch den Aufruf der obigen URL eine Beschreibung dieses Services. Da es bei der WSDL um eine genormte und durch eine XML-Definitionsdatei festgelegte Antwort handelt, können nun Drittsysteme sich auf Basis dieser ihre Routinen mit Hilfe von Tools halbautomatisch entwickeln.

Der Zugriff auf den eigentlichen WebService erfolgt dann mit Hilfe des sogenannten SOAP-Protokolls. SOAP nutzt dabei das Transport-Protokoll HTTP / TCP und XML als Präsentation der Daten. Eine SOAP-Nachricht besteht dabei dann aus einem sog. „Envelope“, der wiederum einen Header und einen Body mit den eigentlichen Nutzdaten enthält. Der Header enthält dabei etwaige Statusinformationen wie beispielsweise eine Anfragekennung oder ähnliches. Im Body befinden sich dann die eigentlichen Nutzdaten.

Man sollte sich bewusst sein, dass durch den Einsatz von XML als Präsentation der Daten sich das Problem ergibt, dass beim Einsatz von SOAP ein sehr hohes Datenvolumen entsteht. So führt beispielsweise das einfache Versenden von „Wahr“ oder „Falsch“ zu einem Datenvolumen von mehreren hundert Bytes, obwohl in einem stark gekoppelten System theoretisch ein Bit reichen würde.

Das Zusammenspiel zwischen WSDL, SOAP und HTTP zeigt folgende Visualisierung:

Interaktion zwischen WSDL, SOAP und HTTP

3. WebServices mit Oracle

3.1 Use-Case der Praxisanwendung

Die Praxisanwendung sammelt halbstündlich Daten über die aktuelle Wettertemperatur für bestimmte Städte durch Aufruf eines kommerziell angebotenen WebServices, bereitet diese intern auf und stellt diese dann wiederum anderen Inhouse-Systemen angereichert als WerbService zur Verfügung. Der Aufruf des externen WebServices, die Anreicherung der Daten sowie das Bereitstellen eines WebServices wurden mittels PL/SQL umgesetzt. In diesem Praxisbericht wird aus Gründen der Übersichtlichkeit nur auf die Bereiche des WebServices-Aufrufes und der WebService-Bereitstellung eingegangen.

Hinweis: In dem hier vorliegenden Praxisbericht wurde der kommerzielle WebService durch einen offenen WebService ersetzt, da der Betreiber des kommerziellen WebServices einer Veröffentlichung seines Namens nicht zugestimmt hat. Der freie WebService bietet aber grds. die gleiche Funktionalität, garantiert jedoch keine entsprechende Verfügbarkeit wieder entsprechende kommerzielle Anbieter.

3.2 Aufruf von WebServices auf Basis von PL/SQL

Für den Aufruf von WebServices aus einer PL/SQL-Routine stellt Oracle seit der Version 10g das Package UTL_DBWS zur Verfügung. Ein Zugriff ist zwar auch mittels UTL_HTTP möglich, aus Gründen der Vereinfachung wurde aber in der hier beschriebenen Projekt der Einsatz von UTL_DBWS gewählt, da ansonsten das Bauen des gesamten SOAP-Requestes per Hand erfolgen müsste. Um dieses Package nutzen zu können, müssen zuvor noch zwei Java-Libaries in die Datenbank geladen werden.

[dos]loadjava -u sys/ -r -v -f -s -grant public -genmissing dbwsclientws.jar dbwsclientdb102.jar[/dos]
Die entsprechenden Libaries finden sich hier.

Um auf das Package UTL_DBWS mittels eines „normalen“ Datenbank-Benutzers zugreifen zu können muss der User „SYS“ noch ein Public Synonym für dieses erzeugen.
CREATE PUBLIC SYNONYM UTL_DBWS FOR SYS.UTL_DBWS;

Um den bereits oben dargestellten Weather-WebService aufzurufen und Daten einzusammeln, wurde folgende Routine implementiert:

CREATE OR REPLACE FUNCTION GETDATAFROMWEBSERVICE RETURN VARCHAR2 IS
v_service             UTL_DBWS.SERVICE;
v_call                UTL_DBWS.CALL;
v_service_qname       UTL_DBWS.QNAME;
v_port_qname          UTL_DBWS.QNAME;
v_operation_qname     UTL_DBWS.QNAME;
v_string_type_qname   UTL_DBWS.QNAME;
v_decimal_type_qname  UTL_DBWS.QNAME;
v_ret                 ANYDATA;
v_retx_string         VARCHAR2(100);
v_retx_len            NUMBER;
v_params              UTL_DBWS.ANYDATA_LIST;
BEGIN
-- Service-Instanz erzeugen...
v_service_qname := UTL_DBWS.TO_QNAME('http://www.webserviceX.NET', 'GlobalWeather');
v_service       := UTL_DBWS.CREATE_SERVICE(v_service_qname);

-- Call-Instanz erzeugen...
v_port_qname      := UTL_DBWS.TO_QNAME('http://www.webserviceX.NET', 'GlobalWeatherSoap');
v_operation_qname := UTL_DBWS.TO_QNAME('http://www.webserviceX.NET', 'GetWeather');
v_call := UTL_DBWS.CREATE_CALL(v_service, v_port_qname, v_operation_qname);

-- Eigenschaften festlegen...
UTL_DBWS.SET_TARGET_ENDPOINT_ADDRESS(v_call,
'http://www.webservicex.net/globalweather.asmx');
UTL_DBWS.SET_PROPERTY(v_call, 'ENCODINGSTYLE_URI', 'http://schemas.xmlsoap.org/soap/encoding/');

-- Parameter hinzufuegen...
v_string_type_qname := UTL_DBWS.TO_QNAME('http://www.w3.org/2001/XMLSchema', 'string');
UTL_DBWS.ADD_PARAMETER(v_call, 'param0', v_string_type_qname, 'ParameterMode.IN');
UTL_DBWS.ADD_PARAMETER(v_call, 'param1', v_string_type_qname, 'ParameterMode.IN');

-- Return-Typ festlegen...
v_decimal_type_qname := UTL_DBWS.TO_QNAME('http://www.w3.org/2001/XMLSchema', 'string');
UTL_DBWS.SET_RETURN_TYPE(v_call, v_decimal_type_qname);

-- Web-Service starten...
v_params(0) := ANYDATA.CONVERTVARCHAR('Hamburg');
v_params(1) := ANYDATA.CONVERTVARCHAR('Germany');
v_ret := UTL_DBWS.INVOKE(v_call, v_params);
DBMS_OUTPUT.PUT_LINE('Result: ' || v_ret.accessvarchar2 );
UTL_DBWS.RELEASE_SERVICE(v_service);
RETURN v_ret.accessvarchar2;
END;
/

Der Aufruf der oben stehende Methodik erfolgt dann mittels einer anderen PL/SQL-Prozedur, welche dann wieder über den Oracle-Scheduler in definierten regelmäßigen Abständen aufgerufen wird. Die als XML zurückgelieferten Daten werden dann mittels der in PL/SQL zur Verfügung gestellten XML-Hilfsmethoden geparst, ausgelesen, angereichert und in die entsprechenden Zieltabellen geschrieben.

Die gleiche Funktionalität zum Abruf der Daten kann dann auch mittels UTL_HTTP wie folgt umgesetzt werden:

CREATE OR REPLACE FUNCTION GETDATAFROMWEBSERVICE RETURN VARCHAR2 IS
http_req  utl_http.req;
http_resp utl_http.resp;
request_env varchar2(32767);
response_env varchar2(32767);
begin
request_env:='<!--?xml version="1.0" encoding="UTF-8" standalone="no"?--><soap-env:envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.webserviceX.NET" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap-env:body><tns:getweather xmlns:tns="http://www.webserviceX.NET"><tns:cityname>Hamburg</tns:cityname><tns:countryname>Germany</tns:countryname></tns:getweather></soap-env:body></soap-env:envelope>';
dbms_output.put_line('Length of Request:' || length(request_env));
dbms_output.put_line ('Request: ' || request_env);

http_req := utl_http.begin_request('http://www.webservicex.net/globalweather.asmx', 'POST', utl_http.HTTP_VERSION_1_1);
utl_http.set_header(http_req, 'Content-Type', 'text/xml; charset=utf-8');
utl_http.set_header(http_req, 'Content-Length', length(request_env));
--utl_http.set_header(http_req, 'SOAPAction', '"http://www.webserviceX.NET/GetWeather"');
utl_http.write_text(http_req, request_env);

dbms_output.put_line('');

http_resp := utl_http.get_response(http_req);
dbms_output.put_line ( 'Status: ' || http_resp.status_code );
dbms_output.put_line ( 'Status-Message: ' || http_resp.reason_phrase );

utl_http.read_text(http_resp, response_env);
dbms_output.put_line('Response: ');
dbms_output.put_line(response_env);

utl_http.end_response(http_resp);
return response_env;
end;
/

3.3 Bereitstellung von WebServices auf Basis von PL/SQL
Damit in PL/SQL geschriebene Routinen als WebService zur Verfügung gestellt werden können, musste bisher ein Java-Wrapper erstellt werden und dieser dann über einen Applikationsserver (wie beispielsweise OC4J oder WebLogic) zur Verfügung gestellt werden. Seit der Version 11g der Oracle-Datenbank ist dies stark vereinfacht worden, so dass kein Applikationsserver mehr benötigt wird. Stattdessen wird nun die sowieso schon vorhandene Funktionalität XML DB genutzt.

Um zu überprüfen, ob das Datenbanksystem bereits Anfragen über einen bestimmten TCP/IP-Port annehmen kann, wird folgender Befehl abgesetzt:
SELECT dbms_xdb.gethttpport FROM dual;

Sollte die Antwort „0“ sein, so muss der Port beispielsweise noch mittels:
exec dbms_xdb.sethttpport(7345);
… zunächst gesetzt werden.

Die Konfiguration der von der Oracle Datenbank angebotenen WebServices erfolgt über die Datei xdbconfig.xml. Damit nicht diese Datei editiert werden muss, kann die Konfiguration dann auch wie folgt als User „SYS“ auf der Datenbank durchgeführt werden. Dabei wird nachfolgend ein Java-Servlet namens „orawsv“ auf „/orawsv/*“ gebunden:

DECLARE
l_servlet_name VARCHAR2(32) := 'orawsv';
BEGIN
DBMS_XDB.deleteServletMapping(l_servlet_name);

DBMS_XDB.deleteServlet(l_servlet_name);

DBMS_XDB.addServlet(
name     => l_servlet_name,
language => 'C',
dispname => 'Weather WebService',
descript => 'Bereitstellung eines WebServices',
schema   => 'XDB');

DBMS_XDB.addServletSecRole(
servname => l_servlet_name,
rolename => 'XDB_WEBSERVICES',
rolelink => 'XDB_WEBSERVICES');

DBMS_XDB.addServletMapping(
pattern => '/orawsv/*',
name    => l_servlet_name);
END;
/

Für die entsprechenden Zugriffsberechtigungen benötigt es noch die Zuweisung der Role XDB_WEBSERVICE zur allgemeinen Freischaltung der Nutzung von WebServices sowie der Role XDB_WEBSERVICE_OVER_HTTP damit die angebotenen WebServices auch über HTTP ansprechbar sind:

GRANT CONNECT, CREATE TABLE, CREATE PROCEDURE TO weather;
GRANT XDB_WEBSERVICES TO weather;
GRANT XDB_WEBSERVICES_OVER_HTTP TO weather;

Für das Auslesen der gespeicherten Informationen wird nun folgende Stored-Procedure bereit gestellt. Diese gibt für einen Ort und ein Land die gerade aktuelle Temperatur zurück:

CREATE OR REPLACE PROCEDURE GET_CURRENT_DEGREE (
v_CITY LOCATION.CITY%TYPE,
v_COUNTRY LOCATION.COUNTRY%TYPE,
v_DEGREES OUT WEATHER.DEGREES%TYPE) AS
BEGIN
SELECT * INTO v_DEGREES FROM
(SELECT DEGREES
FROM WEATHER W, LOCATION L
WHERE W.LOCATION_ID=L.ID
AND UPPER(L.CITY)=UPPER(v_CITY)
AND UPPER(L.COUNTRY)=UPPER(v_COUNTRY)
ORDER BY VALID_FROM DESC)
WHERE ROWNUM=1;
END;
/

Die WSDL kann nun per einfachem Zugriff auf die Oracle-Datenbank mittels HTTP abgefragt werden: http://192.168.0.54:7345/orawsv/WEATHER/GET_CURRENT_DEGREE?wsdl

Dabei ist interessant, dass bei einer solchen Abfrage grds. das Datenbank-Login und das Datenbank-Passwortes des entsprechenden Benutzers abgefragt wird (HTTP-AUTH). Möchte man dies vermeiden, so kann dies mittels <definitions name="GET_CURRENT_DEGREE" targetnamespace="http://xmlns.oracle.com/orawsv/WEATHER/GET_CURRENT_DEGREE" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://xmlns.oracle.com/orawsv/WEATHER/GET_CURRENT_DEGREEGET_CURRENT_DEGREE" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"> <types> <xsd:schema targetnamespace="http://xmlns.oracle.com/orawsv/WEATHER/GET_CURRENT_DEGREE" elementformdefault="qualified"> <xsd:element name="GET_CURRENT_DEGREEInput"> <xsd:complextype> <xsd:sequence> <xsd:element name="V_DEGREES-NUMBER-OUT"> <xsd:complextype> </xsd:complextype></xsd:element> <xsd:element name="V_COUNTRY-VARCHAR2-IN" type="xsd:string"> <xsd:element name="V_CITY-VARCHAR2-IN" type="xsd:string"> </xsd:element></xsd:element></xsd:sequence> </xsd:complextype> </xsd:element> <xsd:element name="GET_CURRENT_DEGREEOutput"> <xsd:complextype> <xsd:sequence> <xsd:element name="V_DEGREES" type="xsd:double"> </xsd:element></xsd:sequence> </xsd:complextype> </xsd:element> </xsd:schema> </types> <message name="GET_CURRENT_DEGREEInputMessage"> <part name="parameters" element="tns:GET_CURRENT_DEGREEInput"> </part></message> <message name="GET_CURRENT_DEGREEOutputMessage"> <part name="parameters" element="tns:GET_CURRENT_DEGREEOutput"> </part></message> <porttype name="GET_CURRENT_DEGREEPortType"> <operation name="GET_CURRENT_DEGREE"> <input message="tns:GET_CURRENT_DEGREEInputMessage"> <output message="tns:GET_CURRENT_DEGREEOutputMessage"> </output></operation> </porttype> <binding name="GET_CURRENT_DEGREEBinding" type="tns:GET_CURRENT_DEGREEPortType"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"> <operation name="GET_CURRENT_DEGREE"> <soap:operation soapaction="GET_CURRENT_DEGREE"> <input> <soap:body parts="parameters" use="literal"></soap:body></soap:operation></operation></soap:binding></binding></definitions> <output> <soap:body parts="parameters" use="literal"> </soap:body></output> <service name="GET_CURRENT_DEGREEService"> <documentation> Weather WebService </documentation> <port name="GET_CURRENT_DEGREEPort" binding="tns:GET_CURRENT_DEGREEBinding"> <soap:address location="http://192.168.0.54:7345/orawsv/WEATHER/GET_CURRENT_DEGREE"> </soap:address></port> </service>

Der WebService ist damit entsprechend nun von anderen Systemen nutzbar. Damit nun nicht ständig die Eingabe eines Logins und Passwort notwendig ist, müsste der Oracle-User einen „Anonymous Access“ erhalten. Dies ist leider laut Oracle derzeit nicht vorgesehen, da dies ein massives Sicherheitsleck darstellen würde.

4. Fazit

Oracle 11g stellt mit der Möglichkeit WebServices direkt aus PL/SQL zu konsumieren als auch PL/SQL Routinen direkt und ohne Applikationsserver bereitzustellen eine komfortable Möglichkeit bereit, um auf einfachste Weise mit WebServices arbeiten zu können. Insbesondere das Bereitstellen von WebServices wurde stark vereinfacht und stellt im Gegensatz zu der Vorgängerversion, bei der man nur mit Hilfe eines Applikationsservers einen WebService, der auf einer PL/SQL-Prozedur basiert, bereitstellen konnte, eine signifikante Verbesserung dar.

Vorteile beim Einsatz vor Oracle als WebService-Provider:

  • Es wird keine weitere Technologie und damit auch kein weiterer Server und eine Software benötigt, die die Aufrufe an den WebService wrappen muss.
  • Es wird keine weitere Zwischenschicht benötigt um auf datenintensive Webservice-Zugriffe zugreifen zu können.

Vorteile beim Einsatz von Oracle als WebService-Consumer:

  • Etwaige ETL-Prozesse, welche Daten regelmäßig über einen WebService abfragen, können direkt in der Datenbank mit PL/SQL umgesetzt werden.
  • Hierdurch entfällt die Entwicklung der ETL-Routinen und das Abfragen des WebServices in einer anderen separaten Programmiersprache.
  • Vermeidung von Roundtrips zwischen einem etwaigen Client, der den WebService abfragt und der Datenbank, da die Daten nicht „hin und her geschoben“ werden müssen.
  • Dadurch, dass die ETL-Prozesse direkt in der Datenbank liegen, entfällt zudem das Deployment und die Wartung weiterer Systeme.

5. Weiterführende Literatur
Bitte werfen Sie einen Blick in
Oracle Database Programming using Java and Web Services und Oracle Web Services Manager für weiterführende Informationen zu diesem Thema.

Autor: Christian Hartmann



Kommentieren

Weitere Empfehlungen:




-->

Datenbanken, Internet Technologien - Nov 19, 2009 9:24 - 0 Kommentare

Oracle WebServices im praktischen Einsatz

More In Datenbanken


Betriebssysteme - Jul 27, 2006 22:28 - 0 Kommentare

Angriff der Klone!

More In Betriebssysteme