Um den praktischen Einsatz dieser Konzepte zu demonstrieren, wird nun ein Anwendungsbeispiel entwickelt, das die Aufgabe hat, die Kommunikation mit einer entfernten Datenbank zu realisieren. Um das Beispiel nicht zu überfrachten, wird hier lediglich die Kommunikationsfunktionalität realisiert, nicht aber der eigentliche Datenbankzugriff. Das vollständige Beispiel, inklusive der Datenbankanwendung, ist in Kapitel 14 dargestellt. In Erweiterung des Beispiels, das in Kapitel 11.4 vorgestellt wurde, wird die Anwendung nun unter Einbezug aktiver Objekte und des Callback-Mechanismus erweitert. Die Schritte, die bei der Ausführung der Anwendung durchlaufen werden, sind daher die folgenden: Ein Client ruft eine Methode zur Aktualisierung einer Datenbank beim Server über RMI auf. Ist das erforderliche Objekt nicht präsent, so wird es über rmid aktiviert. Der Server stellt eine Rückfrage, um mittels eines Passworts die Berechtigung des Clients feststellen zu können. Der Client beantwortet wiederum über RMI die Passwortanfrage. Callback-Operationen Im Folgenden wird zunächst die Funktionalität realisiert, dass der Server auf die Anfrage eines Clients mit einem Callback antworten kann, dass er also vor dem Datenbankzugriff ein Passwort verlangt. Hierzu sind dieselben Schritte zu durchlaufen, wie bei der Entwicklung der bisher betrachteten RMI-Anwendungen. Diese Schritte werden im Folgenden detailliert betrachtet. Definition des Interfaces Zur Abfrage eines Passworts muss zuerst ein zweites Interface definiert werden, das eine Methode enthält, um das Passwort vom Client anzufordern. Dieses Interface wird vom ersten Interface referenziert. package server2; import java.rmi.*; public interface Passwort2 extends Remote { //Anfordern des Passworts public String passwort() throws RemoteException; } Interface 2 package server2; import java.rmi.*; public interface DatabaseUpdate2 extends Remote { //Aktualisierung des Spielstands public int updateDatenbank(int spiel, Passwort2 pwd) throws RemoteException; } Nach der Definition der Interfaces müssen wiederum die Implementierungsklassen erzeugt werden. Implementierungsklassen Die erste Implementierungsklasse wird nun beim Server gespeichert und implementiert das oben angegebene Interface DatabaseUpdate2. Die Klasse DatabaseUpdateImpl2 implementiert das Interface DatabaseUpdate2, die Klasse PasswortImpl2 implementiert das Interface Passwort2. package server2; import java.rmi.*; import java.rmi.server.*; import java.io.Serializable; public class DatabaseUpdateImpl2 extends UnicastRemoteObject implements DatabaseUpdate2, Serializable { public DatabaseUpdateImpl2 () throws RemoteException { } public int updateDatenbank (int spiel, Passwort2 pw) throws RemoteException { String clientPasswort=null; //Schlage Passwort in Datenbank nach if (pw.passwort().equals(clientPasswort)){ System.out.println("Passwort OK"); //Hier wird noch keine Funktion realisiert return spiel; } return -1; } } package server2; import java.rmi.*; import java.rmi.server.*; import java.io.Serializable; public class PasswortImpl2 extends UnicastRemoteObject implements Passwort2, Serializable { private String obname, pwd; public PasswortImpl2 () throws RemoteException { } public String passwort() throws RemoteException{ String passwort = null; //Hier muss ein Fenster zur Passwortangabe erscheinen return passwort; } } Es ist zu beachten, dass die Klasse DatabaseUpdateImpl2 auf die Klasse PasswortImpl2 zurückgreift, die aber beim Client ausgeführt wird. Die Erzeugung der Stubs und des Skeletons, die Implementierung und Übersetzung des Server-Programms und der Start der Registry bzw. des Servers sind identisch mit denen des vorangegangenen Beispiels. Anders verhält sich dies bei der Implementierung des Clients. Hier muss eine Instanz der Klasse PasswortImpl2 erzeugt werden, die an den Server übergeben wird. Mittels dieser Instanz kann der Server dann die Methode passwort auf dem Client aufrufen. package server2; import java.rmi.*; public class Klient2 { public static void main (String args[]) { DatabaseUpdate2 du = null; Passwort2 pwd; if (args.length != 2) { System.err.println("Eingabe: java server.Klient2 <Server> <Port>"); System.exit(1); } String server = args[0]; int port = Integer.parseInt(args[1]); //Neuer Security Manager System.setSecurityManager(new RMISecurityManager()); try { //Binden der Objektinstanz an entfernte Registry String url="//"+server+":"+port+"/DatabaseUpdate2" ; du = (DatabaseUpdate2)Naming.lookup(url); } catch (Exception e) { System.err.println("Keine Verbindung"+e); } try { pwd = new PasswortImpl2(); //Schicke 1, also Spieler hat gewonnen int spielAntwort = du.updateDatenbank(1,pwd); } catch (Exception e) { System.err.println("Problem in Remote-Methode"); } } } Objektaktivierung Im Folgenden soll die Funktionalität der Objektaktivierung realisiert werden. Ein Objekt soll dann aktiv werden, wenn es benötigt wird. Im Beispiel wird daher die Passwortüberprüfung in zwei Teile aufgeteilt: Die erste Klasse erzeugt eine aktivierbare Instanz zur Eingabe von Passwörtern. Nach der Abarbeitung terminiert diese Klasse. Die zweite Klasse greift auf die aktivierbare Instanz zu und bewirkt beim Zugriff die tatsächliche Aktivierung. Ebenso wie das vorangegangene Beispiel ist dieses Beispiel darauf angelegt, eine beidseitige Kommunikation umzusetzen. Aus diesem Grund muss wiederum ein Client-Objekt an den Server übergeben werden, der mittels dieses Objekts Methoden auf dem Client ausführen kann. Zunächst wird wieder das Interface des aktivierbaren Objekts definiert. Es ist zu beachten, dass nun das Passwort3-Objekt Teil der Client-Klasse ist. package server3; import java.rmi.*; public interface DatabaseUpdate3 extends Remote { //Aktualisierung des Spielstands public int updateDatenbank(int spiel, Passwort3 pwd) throws RemoteException; } Die Implementierung dieser Klasse unterscheidet sich nun von der vorangegangenen Implementierung. In diesem Fall wird nicht das Interface UnicastRemoteObject, sondern das Interface Activatable implementiert. package server3; import java.rmi.*; import java.rmi.activation.*; public class DatabaseUpdateImpl3 extends Activatable implements DatabaseUpdate3 { public DatabaseUpdateImpl2 (ActivationID id, MarshalledObject daten) throws RemoteException { //Registrierung bei rmid und Export an anonymen Port super(id, 0); } public int updateDatenbank (int spiel, Passwort3 pw) throws RemoteException { String clientPasswort=null; //Schlage Passwort in Datenbank nach if (pw.passwort().equals(clientPasswort)){ System.out.println("Passwort OK"); //Hier wird noch keine Funktion realisiert return spiel; } return -1; } } Die Client-Seite dieses Beispiels ähnelt stark der Client-Seite des vorangegangenen Beispiels, da Client-Aufrufe eine entfernte Referenz erfordern und der Client daher nicht weiß, dass ein Objekt überhaupt aktivierbar ist. Die Server-Seite ist allerdings unterschiedlich, da der Server das aktivierbare Objekt registrieren muss, bevor er terminiert. Zunächst wird ein Security-Manager eingerichtet. Mittels der Methode put, die Teil der Klasse Properties ist, wird eine Policy-Datei assoziiert, die in diesem Fall der Anwendung alle Rechte einräumt. Es sei aber darauf hingewiesen, dass dieses Vorgehen nur im Testfall sinnvoll ist. Real sollte eine Anwendung niemals mit allen Rechten ausgestattet werden. Die Klasse, die zur Einrichtung des Objekts dient, ist komplexer als Klassen, die UnicastRemoteObject erweitern. Hierbei wird eine Textrepräsentation der URL verwendet, die den Speicherort der aktivierbaren Klasse DatabaseUpdateImpl3 angibt. Ein ActivationGroupID-Objekt wird an ein ActivationDesc-Objekt übergeben, das von rmid registriert wird. Jede neue VM, die mit rmid gestartet wird, wird dann nur Objekte einer einzelnen ActivationGroupID aktivieren. Läuft eine VM bereits, die mit der Klasse einer ActivationGroupID assoziiert ist, so wird das Objekt in der VM erzeugt, anstatt eine neue VM zu starten zu müssen. ActivationGroupID-Objekte ermöglichen daher eine genaue Kontrolle darüber, in welcher VM ein aktiviertes Objekt läuft. In diesem Zusammenhang sei auch auf die Verwendung der Klasse MarshalledObject hingewiesen. Wird ein UnicastRemoteObject-Objekt verwendet, so können Kommandozeilenargumente ohne Probleme an die Implementierungsklasse übergeben werden, da das Server-Programm, das diese Argumente entgegennimmt, während der Lebenszeit der Implementierung des entfernten Objekts immer läuft. Bei aktivierbaren Objekten hingegen kann die einrichtende Klasse nach der Registrierung des Aktivierungsdeskriptors beim RMI-Dämon und nach der Registrierung des Stubs bei der Registry sofort terminieren. Die Klasse MarshalledObject bietet einen flexiblen Mechanismus zur persistenten Übergabe von Daten bzw. zur Initialisierung der Daten über ein ActivationDesc-Objekt, das über rmid registriert ist, an, anstatt Werte hart in der Implementierungsklasse einer Datei zu verdrahten. Im weiteren Verlauf des Programms wird die Methode Activatable.register() aufgerufen, die das ActivationDesc-Objekt an rmid übergibt. Der Deskriptor der Aktivierungsgruppe beinhaltet alle Informationen, die rmid benötigt, um eine Instanz einer aktivierbaren Klasse zu erzeugen. Die Methode Activatable.register() liefert eine entfernte Referenz zurück, die zur Registrierung des aktivierbaren Objekts in der Registry verwendet wird. package server3; import java.net.*; import java.rmi.*; import java.rmi.activation.*; import java.security.*; import java.util.*; public class PasswortStart3 { public static void main (String args[]) { if (args.length != 3) { System.err.println("Eingabe: java server.Datenbank <Server> <Port><Pfad zu Klassendateien>"); System.exit(1); } String server = args[0]; int port = Integer.parseInt(args[1]); String dateiOrt=args[2]; //Security Manager System.setSecurityManager(new RMISecurityManager()); try { //Instanz des Objekts anlegen Properties env = new Properties(); env.put("java.security.policy", "C:/home/fisch/private/Javabuch/buchcode/ka pitel11/p olicy"); ActivationGroupID groupID= ActivationGroup.getSystem().registerGroup(n ew ActivationGroupDesc(env, null)); //Verwende verpacktes Objekt, um dem aktivierten Objekt //mitzuzeilen, wo seine persistenten Daten sind MarshalledObject kommandozeile=null; ActivationDesc ad = new ActivationDesc(groupID, "server.DatabaseUpdateImpl3", dateiOrt, kommandozeile); //Registrierung DatabaseUpdate3 du = (DatabaseUpdate3)Activatable.register(ad); System.out.println("rmid registriert"); //Binden String url = "//"+server+":"+port+"/DatabaseUpdate3"; Naming.rebind(url,du); }catch (Exception e) { e.printStackTrace(); System.out.println(e.getMessage()); } System.exit(0); } } Auf der Client-Seite wird ein Passwort3-Objekt an ein DatabaseUpdate3-Objekt übergeben. Die Klasse Passwort3 ist sehr einfach. package server3; import java.io.Serializable; public class Passwort3 implements Serializable { private String pw; public Passwort3 () { } public String passwort() { String pwd = null; //Erfrage Passwort der Datenbank beim Client return pwd; } } Zum Abschluss des Beispiels muss die Klasse entwickelt werden, die das aktivierbare Objekt aufruft. Diese Klasse erzeugt eine Instanz eines Passwort3-Objekts und verlangt von der Registry eine Referenz auf ein DatabaseUpdate3-Objekt. package server3; import java.rmi.*; public class Klient3 { public static void main (String args[]) { DatabaseUpdate3 du = null; Passwort3 pw = null; if (args.length != 2) { System.err.println("Eingabe: java server.Klient3 <Server> <Port>"); System.exit(1); } String server = args[0]; int port = Integer.parseInt(args[1]); //Neue Passwort3-Instanz pw = new Passwort3(); //Neuer Security Manager System.setSecurityManager(new RMISecurityManager()); try { //Binden der Objektinstanz an entfernte Registry String url="//"+server+":"+port+"/DatabaseUpdate2" ; du = (DatabaseUpdate3)Naming.lookup(url); } catch (Exception e) { System.err.println("Keine Verbindung"+e); } try { //Schicke 1, also Spieler hat gewonnen int spielAntwort = du.updateDatenbank(1,pw); } catch (Exception e) { System.err.println("Problem in Remote-Methode"); } } } Es sollte nicht vergessen werden, die Dateien in der richtigen Reihenfolge aufzurufen. Zuerst müssen rmiregistry und rmid gestartet werden und anschließend ein Passwortobjekt mittels der Anweisung java server3.PasswortStart3 localhost <Port> <Verzeichnis> angelegt werden. Erst im Anschluss kann auf das entfernte Objekt zugegriffen werden.