Java Standard: Corba
Einleitung, Grundbegriffe
Bearbeiten- Verweise zum Thema CORBA
- CORBA
CORBA (Common Object Request Broker Architecture) unterstützt verteilte Anwendungen, die plattformübergreifend also unabhängig von Sprache und Betriebssystem sind. Darin unterscheidet es sich von RMI, das nur zwischen JAVA-Anwendungen möglich ist. CORBA beschreibt die Architektur und ein ORB (Object Request Broker) stellt eine spezielle Implementierung dar.
Ein Server könnte also auf einem Linux-System laufen und in C++ geschrieben sein während ein Client auf einem Windows-PC gestartet wird und in Java geschrieben ist. Der Aufruf selbst ist transparent, d.h. der Nutzer merkt nichts davon, dass die Methode auf einem entfernten Rechner implementiert ist und dort abgearbeitet wird. Parameter und Ergebnis werden transparent zwischen Client und Server ausgetauscht.
CORBA benutzt die IDL (Interface Definiton Language) zur Beschreibung der Schnittstelle zwischen Server und Client, zu der die zu benutzenden entfernten Methoden gehören. Ein IDL-Compiler (für Java 'idlj') generiert daraus die erforderlichen Klassen (stub, skeleton und weitere Hilfsklassen). Im Hintergrund übernehmen diese generierten Klassen stellvertretend für Server/Client Kommunikation und Datenaustausch.
- POA: Portable Object Adapter
Der POA als Teil des ORB vermittelt die Anforderung des Clients (Aufruf der entfernten Methode) an den Server, der diese Methode implementiert hat.
- Vererbungs-Modell (POA):
Die Implementierungsklasse erbt (per 'extends') von der Skeleton-Klasse, die der IDL-Compiler generiert hat. - Delegation-Modell (POA/Tie) benutzt 2 Klassen:
Eine Tie-Klasse erbt vom Skeleton (POA), delegiert aber die Anforderung an die eigentliche Implementierungs-Klasse.
- Anforderungen an die Applikation
Für die Verbindung zwischen Client und Server ist die Schnittstelle zwischen beiden zu definieren (im Beispiel 'Hello.idl').
- CORBA-Server
- Schreibe eine öffentliche Serverklasse nach der Vorlage im Beispiel (HelloServer.java)
- Implementiere in dieser Serverklasse die Interfaces (im Beispiel 'HelloImpl','AdderImpl') in Form von je einer Klasse
- Generiere die Klassen für die Verbindung Client/Server mit dem idlj-Compiler
- Übersetze nun alle Klassen
- CORBA-Client
- Schreibe eine öffentliche Clientklasse nach der Vorlage im Beispiel (HelloClient.java)
- Formuliere in dieser Klasse die Aufrufe der entfernten Methoden.
- Generiere die Klassen für die Verbindung Client/Server mit dem idlj-Compiler
- Übersetze nun alle Klassen
Das Interface für die entfernten Methoden
BearbeitenMuss bei Server und Client verfügbar sein. Es folgt die Datei 'Hello.idl':
//Ein 'module' ist das CORBA Äquivalent zu einem package in Java.
module HelloApp
{
//Rückgabe-Objekt der Methode 'sayHello' (als Java-Klasse).
//Es wird generiert: Standardkonstruktor und Konstruktor mit 3 Parametern,
//die den 3 Strukturelementen entsprechen.
struct Person {
string firstName;
string lastName;
long id;
};
interface Hello
{
//Methode 'sayHello' mit Eingabeparameter (Typ in) gibt Person-Klasse zurück
Person sayHello(in string fname);
//oneway: Client wartet nicht auf das Ende der Methode (shutdown)
oneway void shutdown(); //Server runterfahren
};
//weiteres vom vorigen unabhängiges Interface
interface Adder
{
//2 Variable stehen für get/set-Methoden, bei denen aber der Methodenname mit dem
//Variablennamen übereinstimmt ! Diese Methoden sind beim Server zu
//implementieren und können dann im Client benutzt werden.
attribute string strSumme;
attribute long summe;
//Methode 'add' verteilt Ergebnis auf die Attribute
void add(in long x,in long y);
// oneway void shutdown(); //Server runterfahren
};
};
Der CORBA-Server mit implementierten Methoden
Bearbeitenimport HelloApp.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import org.omg.PortableServer.POA;
import java.util.Properties;
//Implementierung des Hello-Interface
class HelloImpl extends HelloPOA{
private ORB orb;
public void setORB(ORB orb_val){
orb = orb_val;
}
int cnt = 0; //fortlaufende Nummer
//Ergebnis: Person-Objekt wie es im IDL-File definiert wurde
public Person sayHello(String fname){
Person p = new Person(fname,"Mustermann",++cnt);
return p;
}
//wird auf Anforderung vom Client aufgerufen und beendet den Server
public void shutdown(){
orb.shutdown(false);
}
}
//Implementierung des Adder-Interface
class AdderImpl extends AdderPOA{
private ORB orb;
private int summe;
private String strSumme;
public void setORB(ORB orb_val){
orb = orb_val;
}
//'get/set'-Methoden zu den IDL-Anweisungen 'attribute'
public int summe() {
return summe;
}
public void summe(int val) {
summe = val;
}
public String strSumme() {
return strSumme;
}
public void strSumme(String s) {
strSumme = s;
}
//Implementierung zur IDL-Methode 'add'
public void add(int x,int y){
//trage über die Methode 'summe' den Wert in die Variable 'summe' ein
summe(x+y);
strSumme(x + " + " + y + " = " + summe); //analog zum vorherigen
}
public void shutdown(){
orb.shutdown(false);
}
}
public class HelloServer{
public static void main(String args[]){
try{
// Initialisiere ORB und beschaffe Zugang zum 'NameService'
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// Get reference to rootpoa & activate the POAManager
//POA beschaffen und aktivieren
POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
rootpoa.the_POAManager().activate();
// get the root naming context
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
// Use NamingContextExt which is part of the Interoperable
// Naming Service specification.
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
// ************ dieser Teil muss anwenderspezifisch angepasst werden ***********
String helloName = "Hello"; // beim Naming-Service zu registrierender
String adderName = "Adder"; // freier Name der implementierten Methoden
// erzeuge eine Instanz des Hello-Interfaces und unterstelle sie dem ORB
HelloImpl helloImpl = new HelloImpl();
helloImpl.setORB(orb);
// get object reference from the servant(Implementierung der Methode)
org.omg.CORBA.Object ref = rootpoa.servant_to_reference(helloImpl);
Hello href = HelloHelper.narrow(ref);
// verfahre mit dem Adder-Interface analog zum Hello-Interface
AdderImpl adderImpl = new AdderImpl();
adderImpl.setORB(orb);
ref = rootpoa.servant_to_reference(adderImpl);
Adder hrefAdd = AdderHelper.narrow(ref);
//registriere die beiden Instanzen(entfernte Methoden) beim Naming-Service
NameComponent path[] = ncRef.to_name(helloName);
ncRef.rebind(path, href);
path = ncRef.to_name(adderName);
ncRef.rebind(path, hrefAdd);
// ************ Ende anwenderspezifischer Teil ***********
System.out.println("HelloServer ready and waiting ...");
// wait for invocations from clients
orb.run();
}
catch (Exception e){
System.err.println("ERROR: " + e);
e.printStackTrace(System.out);
}
System.out.println("HelloServer Exiting ...");
}
}
Der CORBA-Client
Bearbeitenimport HelloApp.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
public class HelloClient{
/*
Aufruf:
java HelloClient -ORBInitialPort 1050
[-ORBInitialHost nameserverhost] [-shutdown] [-n name] [-val x y]
Parameter:
1050 wählbare Portnummer, die der ORB des Servers auf
Anforderungen abhört
nameserverhost Rechner auf dem der ORB-Dämon (orbd) läuft
name beliebige Zeichenkette als Vorname in der 'person'-Struktur
x, y 2 ganze Zahlen, die addiert werden sollen
*/
public static void main(String args[]){
boolean shutdown = false;
String name = "Moritz";
int sum1 = 12;
int sum2 = 24;
String method = "Hello"; //registrierter Name der implementierten Methode
String methodAdd = "Adder";
//auf eine Prüfung der Parameter wird hier verzichtet
if (args.length > 0) {
for (int i=0; i < args.length; i++) {
if (args[i].startsWith("-shutdown"))
shutdown = true;
else if (args[i].startsWith("-n")) { //Name vorgegeben
name = args[i+1];
i++;
} else if (args[i].startsWith("-val")) { //2 Werte vorgegeben
sum1 = Integer.parseInt(args[i+1]);
sum2 = Integer.parseInt(args[i+2]);
i += 2;
}
}
}
try {
// Initialisiere ORB und beschaffe Zugang zum 'NameService'
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// get the root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
// Use NamingContextExt instead of NamingContext. This is
// part of the Interoperable naming Service.
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
// ************ es folgt der anwenderspezifische Teil ***********
// resolve the Object Reference in Naming
// Die 2 (Teil)Zeichenketten 'Hello' sind identisch mit dem Wert der
// Variablen 'method'(registrierter Name der Methode).
Hello helloImpl = HelloHelper.narrow(ncRef.resolve_str(method));
if (shutdown) { //Server runterfahren
helloImpl.shutdown();
System.out.println("****** Server shutdown completed");
System.exit(1);
}
System.out.println("Obtained a handle on server object: " + method);
// resolve the Object Reference in Naming
Adder adderImpl = AdderHelper.narrow(ncRef.resolve_str(methodAdd));
System.out.println("Obtained a handle on server object: " + methodAdd);
//beide entfernten Methoden werden hier 2-mal aufgerufen
for (int i=1; i<=2; i++) {
if (i == 2) {
name = "Stephan";
sum1 = 26;
sum2 = 4;
}
//Aufruf der entfernten Methode
Person p = helloImpl.sayHello(name);
System.out.println(p.firstName+" "+p.lastName+", "+p.id);
adderImpl.add(sum1,sum2); //ruft die beiden 'set'-Methoden
//benutze nun die 'get'-Methoden
System.out.println("++++++ Summe=" + adderImpl.summe());
System.out.println("++++++ Aufgabe:" + adderImpl.strSumme());
}
} catch (Exception e) {
System.out.println("ERROR : " + e) ;
e.printStackTrace(System.out);
}
}
}
Ablauf für die Abarbeitung des Beispiels
BearbeitenDie Programme 'idlj', 'orbd' sind Bestandteil des JDK.
Sind Server und Client auf dem gleichen Rechner, sollten für sie 2 unabhängige Verzeichnisse gewählt werden.
Zum Beispiel: c:\basispfad\Server und c:\basispfad\Client
- Server und Client
- Generierung der erforderlichen Klassen mit dem IDL-Compiler:
- idlj -v -fall Hello.idl
- Client
- Übersetzung aller Java-Klassen:
- javac *.java HelloApp/*.java
- Server
- Übersetzung aller Java-Klassen:
- javac *.java
- Start des ORB-Dämons (verantwortlich u.a. für den Naming-Service), überwacht Port 1050.
- Die Port-Nr. kann geändert werden, muss aber bei Server und Client übereinstimmen.
- start /b orbd -ORBInitialPort 1050
- bzw. unter UNIX als Hintergrund-Prozess:
- orbd -ORBInitialPort 1050 &
- Start des Servers:
- java HelloServer -ORBInitialPort 1050
- Client
- Start des Clients:
- java HelloClient -ORBInitialPort 1050 -ORBInitialHost nameserverhost
- Wenn der Client auf einem 2. Rechner gestartet wird, ist unter 'nameserverhost' der Rechner mit dem
- ORB-Dämon anzugeben. Andernfalls kann '-ORBInitialHost nameserverhost' weggelassen werden.
Hinweis für Windows-Nutzer:
Man kann den Start von orbd und Server und die Beendigung von orbd nach einem Server-shutdown in einer Batch-Datei zusammenfassen. Ein Beispiel ist die beiliegende Datei 'server.bat'. Das Tool 'pskill' ist unter der Verweisadresse verfügbar.
Ggf. kann 'orbd.exe' auch über den Task-Manager beendet werden.
@echo off set port=1050 if not "%1" == "" set port=%1 @echo on start /b orbd -ORBInitialPort %port% java HelloServer -ORBInitialPort %port% pskill -t orbd.exe
Ein Corba-Generator
BearbeitenBeschreibung
Bearbeiten- Zielstellung:
- Es werden 3 Template-Dateien vorgegeben, aus denen mit dem Programm 'CorbaGenerator' 3 Corba-taugliche Java-Quellen erzeugt werden.
- Diese enthalten alle Corba-spezifischen Codeteile, die für die Kommunikation zwischen Client und Server erforderlich sind.
- Der Anwender muss in den generierten Quellen nur noch die Aufgaben-spezifischen Teile ergänzen.
- Templates:
- Die Template-Vorgaben sind 'Server.templ', 'Servant.templ' (zur Implementierung einer Methode auf dem Server), 'Client.templ'.
- Der Anwender muss die IDL-Datei zur Definition der Schnittstelle Client/Server bereitstellen.
- Aufruf:
- java CorbaGenerator idl_file
- Parameter:
- Name der IDL-Datei. In dieser sind nur 1 'module'-Anweisung, aber mehrere 'interface'-Anweisungen möglich.
- Die Werte dieser Anweisungen sind für die Generierung erforderlich. Sie müssen von Leerzeichen umgeben sein.
- Die Anweisungen dürfen jedoch in Spalte 1 beginnen und unmittelbar nach dem 2. Wert enden.
- Ergebnis:
- Die generierten Quellen müssen noch anwendungsspezifisch angepasst werden:
- Implementierung der jeweiligen Methode in der 'Servant'-Quelle und
- Implementierung der Methoden-Aufrufe in der 'Client'-Quelle.
- Ablauf einer Abarbeitung:
- a) Aufruf 'idlj'-Compiler zur Generierung der Hilfsklassen aus der IDL-Datei
- b) Generierung der Corba-tauglichen Java-Quellen aus den Templates
- c) Anpassung der Quellen aus Punkt b
- d) Übersetzung aller Java-Quellen
- e) Start 'orbd'-Dämon und Server
- f) Start von Clients
Die Templates
Bearbeitenimport org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import !!module!!.*;
public class Server {
public static void main(String args[]) {
try{
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// Get reference to rootpoa & activate the POAManager
//POA beschaffen und aktivieren
POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
rootpoa.the_POAManager().activate();
// get the root naming context
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
// Use NamingContextExt which is part of the Interoperable
// Naming Service specification.
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
String namingId;
NameComponent path[];
!!methodRegistering!!
System.out.println("Server started....");
// wait for invocations from clients
orb.run();
} catch(Exception e) {
System.err.println("ERROR: " + e.getMessage());
e.printStackTrace(System.out);
}
}
}
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import !!module!!.*;
class !!interface!!Servant extends !!interface!!POA {
private ORB orb;
public void setORB(ORB orb_val){
orb = orb_val;
}
// ************************ Implementierung der Methode ************************
// *****************************************************************************
//wird auf Anforderung vom Client aufgerufen und beendet den Server
public void shutdown(){
orb.shutdown(false);
}
}
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import !!module!!.*;
public class Client {
public static void main(String argv[]) {
try {
// create and initialize the ORB
ORB orb = ORB.init(argv, null);
// get the root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
String namingId;
!!clientNaming!!
// ************ Aufruf der Methoden:
// *****************************************************************************
} catch(Exception e) {
System.out.println("FileClient Error: " + e.getMessage());
e.printStackTrace();
}
}
}
Der Corba-Generator
Bearbeitenimport java.io.*;
import java.util.ArrayList;
public class CorbaGenerator {
public static void main(String args[]) {
if (args.length < 1) {
System.out.println(
"Usage: java CorbaGenerator idl_file");
System.exit(1);
}
String module = null;
//ArrayList für evtl. mehrere 'interface'-Anweisungen
ArrayList<String> pinterface = new ArrayList<String>();
try {
pinterface = getIdlValues(args[0]);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
if (pinterface == null)
System.exit(1);
module = pinterface.remove(0);
System.out.println("module="+module);
for (String el: pinterface) {
System.out.println("interface="+el);
}
CorbaGenerator cg = new CorbaGenerator();
//bearbeite die 3 Templates 'Server', 'Servant', 'Client'
for (int i = 0; i < 3; i++) {
cg.worker(i,module,pinterface);
}
}
//IDL-Datei lesen und die Werte der Anweisungen 'modulname' und 'interface'
//entnehmen
static ArrayList<String> getIdlValues(String fileName) throws Exception {
String line;
ArrayList<String> v = new ArrayList<String>();
BufferedReader in = null;
in = new BufferedReader(new FileReader(fileName));
int m = 0;
int i = 0;
while ((line = in.readLine()) != null) {
line = line.trim();
if ( line.startsWith("module ") ) {
v.add(line.split(" ")[1]);
m++;
} else if ( line.startsWith("interface ") ) {
v.add(line.split(" ")[1]);
i++;
}
}
String fehler = null;
if (m != 1) {
fehler = "Es muss genau 1 module-Anweisung vorhanden sein !";
} else if (i == 0) {
fehler = "Keine interface-Anweisung gefunden !";
}
if (fehler != null) {
System.out.println("*** IDL-Fehler: " + fehler);
v = null;
}
return v;
}
String[] templates = {"Server", "Servant", "Client"};
String result;
String outFile = null;
//anzupassendes Codestück für Server
String methodRegistering = new String(
"namingId = \"!!interface!!\";\n"+
"!!interface!!Servant !!interface!!Impl = new !!interface!!Servant();\n"+
"!!interface!!Impl.setORB(orb);\n"+
"objRef = rootpoa.servant_to_reference(!!interface!!Impl);\n"+
"!!interface!! !!interface!!Ref = !!interface!!Helper.narrow(objRef);\n"+
"path = ncRef.to_name(namingId);\n"+
"ncRef.rebind(path, !!interface!!Ref);\n");
//anzupassendes Codestück für Client
String clientNaming = new String(
"// ************ Instanz der Methode, Aufruf: !!interface!!Impl.methodenname(...);\n"+
"namingId = \"!!interface!!\";\n"+
"!!interface!! !!interface!!Impl = !!interface!!Helper.narrow(ncRef.resolve_str(namingId));\n");
//Aus den Codestücken wird durch Ersetzen der Platzhalter (!!......!!) mit
//den Werten aus der IDL-Datei Quellcode erzeugt, der dann in der Template-
//Datei den dort vorgesehenen Platzhalter ersetzt.
void worker(int indTemplate, String pmodule, ArrayList<String> pinterface) {
String template = templates[indTemplate];
String fileName = template + ".templ";
File file = new File(fileName);
char buffer[] = new char[(int)file.length()];
String module = "!!module!!";
String interFace = "!!interface!!";
StringBuffer sb = new StringBuffer();
try {
//Template-Datei lesen
BufferedReader input = new BufferedReader(new FileReader(fileName));
input.read(buffer,0,buffer.length);
input.close();
String zws = new String(buffer);
//bearbeite import-Anweisung
result = zws.replaceFirst(module,pmodule);
if (indTemplate == 0) { //Server
for (String el: pinterface) { //ggf. mehrere Interfaces
//Instanz der Servant-Klasse beginnt mit Kleinbuchst. endet mit "Impl"
String z = ("" + el.charAt(0)).toLowerCase();
//Name der Methode soll mit Kleinbuchstaben beginnen
String tmp = methodRegistering.replaceAll(
interFace + "Impl",z+el.substring(1) + "Impl");
//ersetze nun alle anderen Platzhalter
sb.append(tmp.replaceAll(interFace,el));
}
//einsetzen des soeben erzeugten StringBuffer's in das Template
result = result.replaceFirst("!!methodRegistering!!",sb.toString());
} else if (indTemplate == 2) { //Client
for (String el: pinterface) {
String z = ("" + el.charAt(0)).toLowerCase();
String tmp = clientNaming.replaceAll(interFace + "Impl",z+el.substring(1) + "Impl");
sb.append(tmp.replaceAll(interFace, el));
}
result = result.replaceFirst("!!clientNaming!!",sb.toString());
}
if (indTemplate == 1) { //Servant
for (String el: pinterface) {
//Name der zu generierenden Datei
outFile = el + template + ".java";
result = zws.replaceFirst(module,pmodule); //import-Anweisung
result = result.replaceAll(interFace,el); //class-Anweisung
ausgabe(); //jede Methoden-Implementierung als eigene Klasse
}
} else {
outFile = pmodule + template + ".java";
result = result.replaceFirst("class " + template, "class " + pmodule + template);
ausgabe();
}
} catch(Exception e) {
System.out.println("CorbaGenerator Error: "+e.getMessage());
e.printStackTrace();
}
}
void ausgabe() throws IOException {
BufferedWriter out = new BufferedWriter(new FileWriter(outFile,false));
out.write(result);
out.close();
}
}
Bei den 3 Template-Dateien werden Zeichenketten vom Typ !!identifier!! (Bsp.: !!clientNaming!!) durch passenden Code ersetzt, den der Generator erzeugt. Die Namen der generierten Dateien enthalten den IDL-modulname bzw. das IDL-interface. Beispiel, IDL:
- modulname HelloApp
- interface Hello
Es entstehen die Java-Dateien:
- HelloAppServer, HelloServant, HelloAppClient
Der Server ist bereits komplett und muss nicht mehr angepasst werden.
Bei Servant und Client sind Ergänzungen in dem markierten Kommentarbereich vorzunehmen. Eventuell müssen noch im Kopfbereich erforderliche import-Anweisungen eingefügt werden.