Metasuche
aus GenWiki, dem genealogischen Lexikon zum Mitmachen.
Die einzelnen Suchmaschinen stelle die Abfrage als Webservice zur Verfügung. Da alle Suchmaschinen das gleiche Interface verwenden, braucht im Metasuche-Programm nur noch die URL gespeichert zu werden.
Inhaltsverzeichnis |
[bearbeiten] Suchmaschinen-Seite
Auf der Suchmaschinen Seite braucht man die Methode:
- search( lastname: string, placename: string ) : list
zu implementieren, wobei von den beiden Parametern einer auch weggelassen werden kann.
Zurückgegeben wird eine Liste von Entry-Objekten, die folgende (jeweils optionale) Attribute haben:
- lastname: string
- firstname: string
- details: string
[bearbeiten] Antwort
Die Antwort sieht dann so aus:
<meta:search-result xmlns:meta='http://meta.genealogy.net/searchresult'> <meta:database> <meta:name>''Name der Suchmaschine''</meta:name> <meta:url>''URL der Suchmaschine (z.B. Startseite)''</meta:url> <meta:entry> <meta:lastname>Meier/meta:lastname> <meta:firstname>Max</meta:firstname> <meta:details>* 1. Apr 1900 Hamburg</meta:details> <meta:url>''Verweis zum Datenblatt von Max Meier''</meta:url> </meta:entry> </meta:database> </meta:search-result>
Die Suchmaschine kann beliebig viele "entry"-Elemente schicken, eine gute Wahl sind vielleicht 20 Stück.
Wenn es noch mehr Treffer gibt, die aber nicht übertragen werden, wird in das "database"-Element das Element
<meta:has-more/>
eingefügt.
Eine Antwort kann auch mehr als ein "database"-Element enthalten.
[bearbeiten] Metasuche-Seite
Die Metasuchmaschine stellt nach außen den gleichen Webservice wie eine andere Suchmaschine bereit.
Bei Start einer Anfrage wird für jede zu durchsuchende Suchmaschine (durch URL bestimmt) ein Thread SearchAgent gestartet, der eine Anfrage an den Webservice 'seiner' Suchmaschine startet. Sobald ein Ergebnis eintritt, wird dies dem Suchergebnis-Objekt hinzugefügt.
Nach einer vorgegebenen Wartezeit werden alle Threads beendet.
[bearbeiten] Webservice Description
<?xml version="1.0"?> <wsdl:definitions name="genealogy.net-metasearch" targetNamespace="http://meta.genealogy.net/ws" xmlns:metaWS="http://meta.genealogy.net/ws" xmlns:meta="http://meta.genealogy.net/searchresult" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" > <wsdl:types> <xsd:schema targetNamespace="http://meta.genealogy.net/searchresult" elementFormDefault="qualified" > <xsd:complexType name="SearchResult"> <xsd:sequence> <xsd:element name="database" minOccurs="0" maxOccurs="unbounded" type="meta:Database"/> <xsd:element name="timeout" maxOccurs="unbounded" minOccurs="0" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="Database"> <xsd:sequence> <xsd:element name="entry" type="meta:Entry" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="name" type="xsd:string"/> <xsd:attribute name="url" type="xsd:anyURI" minOccurs="0"/> <xsd:attribute name="has-more" type="xsd:boolean" minOccurs="0"/> </xsd:complexType> <xsd:complexType name="Entry"> <xsd:sequence/> <xsd:attribute name="lastname" type="xsd:string" minOccurs="0"/> <xsd:attribute name="firstname" type="xsd:string" minOccurs="0"/> <xsd:attribute name="details" type="xsd:string" minOccurs="0"/> <xsd:attribute name="url" type="xsd:anyURI" minOccurs="0"/> </xsd:complexType> </xsd:schema> </wsdl:types> <wsdl:message name="SearchRequest"> <wsdl:part name="lastname" type="xsd:string"/> <wsdl:part name="placename" type="xsd:string"/> </wsdl:message> <wsdl:message name="SearchResult"> <wsdl:part name="search-result" type="meta:SearchResult" /> </wsdl:message> <wsdl:message name="EmptyMessage"/> <wsdl:message name="VersionMessage"> <wsdl:part name="version" type="xsd:positiveInteger"/> </wsdl:message> <wsdl:portType name="SearchPortType"> <wsdl:operation name="search"> <wsdl:input message="metaWS:SearchRequest"/> <wsdl:output message="metaWS:SearchResult"/> </wsdl:operation> <wsdl:operation name="getVersion"> <wsdl:input message="metaWS:EmptyMessage"/> <wsdl:output message="metaWS:VersionMessage"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="SearchSoapBinding" type="metaWS:SearchPortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="search"> <soap:operation soapAction="http://meta.genealogy.net/ws/search"/> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> <wsdl:operation name="getVersion"> <soap:operation soapAction="http://meta.genealogy.net/ws/search"/> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="Metasearch"> <wsdl:port name="SearchPort" binding="metaWS:SearchSoapBinding"> <soap:address location="http://localhost"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
[bearbeiten] Schritt für Schritt Anleitung - PHP5
Ein Hinweis vorweg: Ich kenne mich mit PHP so gut wie nicht aus und schreibe diese Anleitung, während ich es selbst ausprobiere...
Warum PHP5? Erst die Version 5 enthält mit der "PHP SOAP Extension" eine vernünftige Implementierung um mit Webservices zu arbeiten.
Eine schöne Einführung wie man Webservices mit PHP nutzt, gibt es hier: http://www.zend.com/php5/articles/php5-SOAP.php?article=php5-SOAP&kind=php5&id=4704&open=1&anc=0&view=1
[bearbeiten] Kopieren des WSDL-Dokuments
Eine Datei metasearch.wsdd anlegen und den unter #Webservice Description stehenden Text dort hineinkopieren. Ganz am Ende gibt es eine Zeile, die den Text "http://localhost" enthält. Diese Adresse müssen wir durch die Adresse des zu schreibenden PHP-Skripts ersetzen. Bei mir ist es zum Beispiel http://localhost/~jesper/meta/server.php
[bearbeiten] Test Client
Zum Testen baue ich mir im selben Verzeichnis einen Client client.php, der eine Anfrage an die Metasuche schickt.
<?php $client = new SoapClient("metasearch.wsdl"); echo "<pre>\n"; print($client->getVersion()); echo "\n"; print($client->search("Meyer","")); echo "\n</pre>\n"; ?>
[bearbeiten] das Server-Skript
Ein großer Teil des Skriptes besteht aus der Definition der drei Klassen, die im Webservice verwendet werden. Diese kann man natürlich noch mit nützlichen Methoden ausstatten. Ich würde mich freuen, wenn wir gute Implementierungen hier sammeln könnten.
<?php class SearchResult { public $database; public $timeout; } class Database { public $entry; public $name; public $url; public $hasMore; } class Entry { public $lastname; public $firstname; public $details; public $url; } class MetasearchServer { function search( $name, $place ) { # Ergebnis erzeugen return $result; } function getVersion() { return 1; } } $server = new SoapServer("metasearch.wsdl"); $server->setClass("MetasearchServer"); $server->handle(); ?>
Damit auch Daten übertragen werden, erzeuge ich in der search Funktion diese Dummy-Daten:
$entry = new Entry();
$entry->lastname='Meyer';
$entry->firstname='Max';
$entry->details='Schönberg';
$entry->url='http://foo.bar/1';
$db1 = new Database();
$db1->name='Test1';
$db1->url='http://foo.bar';
$db1->hasMore=true;
$db1->entry = array($entry);
$db2 = new Database();
$db2->name='Test2';
$db2->url='http://localhost';
$db2->hasMore=false;
$db2->entry = array();
$result = new SearchResult();
$result->database = array($db1,$db2);
[bearbeiten] Schritt für Schritt Anleitung - Java
[bearbeiten] Generieren der Server-Klassen
export CLASSPATH=.:~/axis-1_2_1/lib/axis.jar:~/axis-1_2_1/lib/commons-discovery-0.2.jar:~/axis-1_2_1/lib/commons-logging-1.0.4.jar:~/axis-1_2_1/lib/jaxrpc.jar:~/axis-1_2_1/lib/log4j-1.2.8.jar:~/axis-1_2_1/lib/saaj.jar:~/axis-1_2_1/lib/wsdl4j-1.5.1.jar java org.apache.axis.wsdl.WSDL2Java -T 1.2 -S true --NStoPkg http://meta.genealogy.net/ws=net.genealogy.gedbas.metasearch --NStoPkg http://meta.genealogy.net/searchresult=net.genealogy.gedbas.metasearch metasearch-rpc.wsdl
Die beiden --NStoPkg sorgen dafür, daß die generierten Klassen in einem Paket landen, das zur Struktur des Projektes paßt.
Erzeugt werden folgende Dateien:
net/
genealogy/
gedbas
metasearch/
Database.java
deploy.wsdd
Entry.java
Metasearch_BindingImpl.java
Metasearch_BindingSkeleton.java
Metasearch_BindingStub.java
Metasearch_PortType.java
Metasearch_Service.java
Metasearch_ServiceLocator.java
SearchResult.java
undeploy.wsdd
[bearbeiten] Ausprogrammieren der Server-Klassen
Änderungen müssen nur noch an SearchSoapBindingImpl.java vorgenommen werden. Zunächst enthält sie zwei leere Methoden:
public net.genealogy.meta.searchresult.SearchResult search(java.lang.String lastname, java.lang.String placename) throws java.rmi.RemoteException {
return null;
}
public int getVersion() throws java.rmi.RemoteException {
return null;
}
Die Anpassungen in getVersion sind trivial:
public int getVersion() throws java.rmi.RemoteException {
return 1; // derzeit ist Version 1 aktuell
}
search muß entsprechend der eigenen Suchlogik angepaßt werden. Am Beispiel von GedBas sieht das so aus:
public net.genealogy.meta.searchresult.SearchResult search(
java.lang.String lastname, java.lang.String placename)
throws java.rmi.RemoteException {
final net.genealogy.gedbas.Search searchRequest = new net.genealogy.gedbas.Search();
searchRequest.setLastname(lastname);
searchRequest.setPlacename(placename);
// Es sollen 50 Treffer zurückgeliefert werden. Wir fordern 51 an -
// kommen diese 51, wird das Attribut hasMore gesetzt
searchRequest.setResultsPerPage(NUMBER_OF_RESULTS + 1);
try {
final java.util.List interimResult = net.genealogy.gedbas.GedBas
.getInstance().sucheDurchfuehren(searchRequest);
/*
* Ein SearchResult Objekt zusammenstellen: Wir bauen ein
* Database-Objekt mit den festen Werden für GedBas
*/
final net.genealogy.meta.searchresult.Database[] database =
new net.genealogy.meta.searchresult.Database[1];
final net.genealogy.meta.searchresult.SearchResult result =
new net.genealogy.meta.searchresult.SearchResult(database);
database[0] = new net.genealogy.meta.searchresult.Database();
database[0].setName("GedBas");
database[0].setUrl(new URI("http://gedbas.genealogy.net"));
if (interimResult.size() > NUMBER_OF_RESULTS) {
/*
* wenn es mehr als 50 Treffer gibt, wird das "hasMore"
* Attribute gesetzt
*/
database[0].setHasMore("true");
/* das Test-Element wieder entfernen */
interimResult.remove(NUMBER_OF_RESULTS); //
}
/* die einzelnen Ergebnisse kopieren */
final Entry[] entries = new Entry[interimResult.size()];
database[0].setEntry(entries);
for (int i = 0; i < interimResult.size(); i++) {
net.genealogy.gedbas.SearchResult entry = (SearchResult) interimResult;
entries[i] = new Entry(entry.getLastname(),
entry.getFirstname(),
entry.getDetails(), new URI(URL_PREFIX+ entry.getId()
));
}
return result;
} catch (Exception e) {
throw new RemoteException(e.getMessage());
}
}
Das Ausprogrammieren hat bei mir vielleicht 15 Minuten gedauert. Bei GedBas habe ich den Vorteil, daß ich die Details sowieso schon in einem Feld zusammen habe, sonst müßte man hier noch kurz ein paar Werte zusammenfassen.
[bearbeiten] Vorbereiten der Webapplikation für Webservices
Nun muß der Webservice veröffentlicht werden. Ich gehe für die Beschreibung davon aus, daß bisher noch kein Webservice aktiv ist. In der WEB-INF/web.xml sind folgende Angaben zu ergänzen:
<servlet>
<servlet-name>AxisServlet</servlet-name>
<display-name>Apache-Axis Servlet</display-name>
<servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AxisServlet</servlet-name>
<url-pattern>/servlet/AxisServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>AxisServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
Nach dem Neuladen der Webapplikation sollte unter /applikation/servlet/AxisServlet der Begrüßungsbildschirm des Axis-Servlet zu sehen sein.
[bearbeiten] Deployen des neuen Webservices
Anschließend kann der neu geschriebene Webservice deployed werden:
java org.apache.axis.client.AdminClient -l http://localhost:8080/applikation/servlet/AxisServlet net/genealogy/meta/ws/deploy.wsdd
Der Pfad zum AxisServlet ist natürlich der eigenen Webapplikation entsprechend anzupassen.
