Java Standard: Corba


Einleitung, GrundbegriffeBearbeiten

  • Verweise zum Thema CORBA
Vorlage zum Beispiel
Beschreibung CORBA
Überblick: RMI, CORBA, IDL
  • 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.

  1. Vererbungs-Modell (POA):
    Die Implementierungsklasse erbt (per 'extends') von der Skeleton-Klasse, die der IDL-Compiler generiert hat.
  2. 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
  1. Schreibe eine öffentliche Serverklasse nach der Vorlage im Beispiel (HelloServer.java)
  2. Implementiere in dieser Serverklasse die Interfaces (im Beispiel 'HelloImpl','AdderImpl') in Form von je einer Klasse
  3. Generiere die Klassen für die Verbindung Client/Server mit dem idlj-Compiler
  4. Übersetze nun alle Klassen
  • CORBA-Client
  1. Schreibe eine öffentliche Clientklasse nach der Vorlage im Beispiel (HelloClient.java)
  2. Formuliere in dieser Klasse die Aufrufe der entfernten Methoden.
  3. Generiere die Klassen für die Verbindung Client/Server mit dem idlj-Compiler
  4. Übersetze nun alle Klassen

Das Interface für die entfernten MethodenBearbeiten

Muss 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 MethodenBearbeiten

import 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-ClientBearbeiten

import 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 BeispielsBearbeiten

Die 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

  1. Server und Client
    Generierung der erforderlichen Klassen mit dem IDL-Compiler:
    idlj -v -fall Hello.idl
  2. Client
    Übersetzung aller Java-Klassen:
    javac *.java HelloApp/*.java
  3. 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
  4. 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-GeneratorBearbeiten

BeschreibungBearbeiten

  1. 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.
  2. 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.
  3. Aufruf:
    java CorbaGenerator idl_file
  4. 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.
  5. 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.
  6. 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 TemplatesBearbeiten

Server.templ

import 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);
    }
  }
}
Servant.templ

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);
  }
}
Client.templ

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-GeneratorBearbeiten

CorbaGenerator.java

import 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.