CORBA fornisce una chiara separazione tra l'interfaccia di un oggetto e la sua implementazione. Un client non si deve occupare in modo diretto dei dettagli di implementazione, ma solo dell'interfaccia implementata dall'oggetto che intende utilizzare. La definizione dell'interfaccia di un oggetto CORBA è realizzata mediante il linguaggio IDL. In questo articolo si vedranno le basi di IDL e le regole di mapping con Java
Introduzione
Nel middleware CORBA tutti gli oggetti sono trattati come interfacce. Questo vale sia per gli oggetti che compongono l'infrastruttura (ORB, Services, ecc.), sia per i comuni oggetti applicativi. Un client non si dovrà quindi occupare dei dettagli di implementazione, ma solo dell'interfaccia implementata dall'oggetto che intende utilizzare (in modo simile a quanto accade in RMI).
Come si è già visto in [1] CORBA è trasparente rispetto al linguaggio. La definizione dell'interfaccia di un oggetto CORBA non è quindi realizzata mediante un comune linguaggio di programmazione, ma mediante il linguaggio IDL specificato da OMG (in molti dei linguaggi utilizzabili con CORBA, come COBOL e C, non esiste neppure il concetto di interfaccia). L'implementazione è realizzata invece utilizzando un normale linguaggio di programmazione per cui sia stato definito un mapping con IDL.
Sintassi e caratteristiche base
IDL è un linguaggio puramente descrittivo che permette di specificare solo gli aspetti relativi all'interfaccia di un oggetto. Si potranno quindi definire le signature dei metodi, le eccezioni che questi rilanciano, l'appartenenza ai package, costanti e strutture dati manipolate dai metodi.
La sintassi IDL è C-like ed è di conseguenza piuttosto simile alla sintassi Java. Sebbene sia un linguaggio descrittivo orientato agli oggetti, in modo simile al C++, IDL include la possibilità, non contemplata da Java, di definire strutture dati che non siano classi.
I blocchi logici IDL sono racchiusi in parentesi graffe; a differenza di Java è necessario terminare sempre il blocco con un ";". Anche il singolo statement è terminato da un ";". Con "::" è possibile specificare la gerarchia delle classi (equivale al "." Java, per esempio CORBA::Object).
Nelle specifiche si parla di IDL come di un linguaggio case-insensitive, ma esistono implementazioni che non rispettano questa direttiva. A proposito delle regole di naming va notato che CORBA non nasce nel mondo Java e quindi i tool IDL e le interfacce definite da OMG non rispettano le regole di naming abituali in un contesto Java.
In IDL è importante la sequenza delle definizioni dei vari elementi. Non è possibile utilizzare un elemento, sia esso una exception, una struttura dati o un'interfaccia, se non è già stato definito o almeno dichiarato; esiste comunque un meccanismo di forward declaration.
IDL non implementa l'override e l'overload, queste limitazioni sono legate al fatto che molti dei linguaggi supportati non forniscono queste caratteristiche.
A differenza di quanto accade in Java in un file IDL possono esistere molte interfacce pubbliche.
Il compilatore IDL
Data la definizione IDL di uno o più oggetti sarà necessario utilizzare un apposito compilatore fornito a corredo dell'ORB. Dalla compilazione si otterranno un buon numero di file .java, fra cui stub, skeleton ed altri contenenti codice di supporto per l'aggancio all'ORB. A partire dai file generati sarà possibile realizzare l'opportuna implementazione Java.
Gli esempi presenti in questo articolo fanno esplicito riferimento a due implementazioni: Sun Java IDL ed Inprise Visibroker (l'utilizzo di altri ORB non dovrebbe presentare difficoltà significative).
Java IDL attualmente è disponibile in due versioni decisamente differenti. L'implementazione fornita con il JDK 1.2 è limitata ed il compilatore IDL va scaricato a parte da http://developer.java.sun.com/developer/earlyAccess/jdk12/idltojava.html. Una migliore implementazione è presente invece nel Java 2 SDK a partire dalla versione 1.3.
Inprise Visibroker è probabilmente la migliore e più diffusa implementazione CORBA presente sul mercato; è disponibile in versione trial da http://www.inprise.com/visibroker/.
Sarà creata una sottodirectory basic ed un file PI.java
// JAVA
package basic;
public interface PI {
public final static float value = (float)3.14159;
}
La generazione del file operata dal compilatore IDL è basata sulle regole di mapping definite da OMG per il linguaggio Java.
Mapping IDL/Java
La trasposizione da linguaggio IDL a linguaggio Java effettuata dal compilatore si basa sull'insieme di regole definite da OMG che costituiscono il mapping tra i due linguaggi.
Tipi base
La definizione di regole di mapping tra IDL ed un linguaggio di programmazione implica in primo luogo la definizione di corrispondenze tra i differenti tipi di base; a run-time questo può causare errori di conversione durante il marshalling e l'unmarshalling dei dati. La gestione di questi errori a run-time è a carico del programmatore.
Il problema si pone tipicamente per i tipi più ampi in Java che in IDL; ad esempio per i char che in Java, a differenza della maggior parte degli altri linguaggi, sono trattati come Unicode (16 bit) e non ASCII (8 bit). In IDL i tipi che trattano caratteri Unicode sono wchar e wstring.
| IDL Type | Java type | Exceptions | |----------|-----------|------------| | boolean | boolean | | | char | char | CORBA::DATACONVERSION | | wchar | char | CORBA::DATACONVERSION | | octet | byte | | | string | java.lang.String | CORBA::MARSHAL<br>CORBA::DATACONVERSION | | wstring | java.lang.String | CORBA::MARSHAL<br>CORBA::DATACONVERSION | | short | short | | | unsigned short | short | | | long | int | | | unsigned long | int | | | long long | long | | | unsigned long long | long | | | float | float | | | double | double | | | fixed | java.math.BigDecimal | CORBA::DATA_CONVERSION |
Alcuni dei tipi supportati da IDL non trovano corrispondenza in Java (ad esempio i tipi unsigned). TRUE e FALSE in IDL sono costanti e vengono mappate con i literal Java true e false.
Particolare attenzione va prestata all'utilizzo di null. CORBA non ha la nozione di null riferito alle stringhe o agli array. Un parametro stringa dovrà ad esempio essere trattato come una stringa vuota pena l'eccezione org.omg.CORBA.BadParam.
In IDL ogni tipo, base o complesso, può essere associato ad un nome mediante la parola chiave typedef. Poiché in Java il concetto di alias per un tipo non esiste, nel codice generato verranno usati comunque i tipi primitivi che lo compongono.
Module ed interface
Come forse si è notato nell'esempio precedente la parola chiave module viene mappata esattamente sul package Java
// IDL
module basic {...}
// generated Java
package basic;
In IDL la keyword interface permette di specificare la vera e propria interfaccia dell'oggetto remoto definendone i metodi (nel gergo CORBA operazioni).
Il mapping di un'interface è ottenuto con la generazione di un'interfaccia ed alcune classi Java. Definendo la semplice interfaccia IDL
il compilatore creerà una directory basic ed una serie di file Java (usando Visibroker verranno generati anche altri file):
_HelloWorldImplBase: è lo skeleton, la classe base per la generazione dell'oggetto remoto; fornisce i meccanismi di ricezione di una request dall'ORB e quelli di risposta.
_HelloWorldStub: è lo stub, l'implementazione client-side dell'oggetto remoto; fornisce i meccanismi di conversione tra l'invocazione del metodo e l'invocazione via ORB dell'oggetto remoto.
HelloWorldOperations: è l'interfaccia Java che contiene le signature dei metodi.
HelloWorld: è l'interfaccia Java dell'oggetto remoto, specializza HelloWorldOperations.
HelloWorldHelper ed HelloWorldHolder saranno spiegati più avanti
Le interfacce HelloWorldOperations ed HelloWorld definiscono l'interfaccia dell'oggetto CORBA, sono dette rispettivamente operations interface e signature interface. Il JDK 1.2 utilizza vecchie regole di mapping e non genera l'operation interface.
un oggetto di questo tipo dovrà quindi fornire l'implementazione dei due metodi (metodoA e metodoB).
Attributi e metodi
In IDL le signature dei vari metodi sono fornite in maniera simile a Java. Per comodità è possibile dare una definizione dei metodi accessor di un attributo (i classici get e set Java) utilizzando la keyword attribute con l'eventuale modificatore readonly
In IDL il passaggio di parametri ad un metodo implica la dichiarazione del tipo di passaggio che si desidera adottare. Un parametro potrà essere definito in (parametro di input non modificabile dal metodo), out (parametro di output modificabile dal metodo) e inout.
Per supportare il passaggio di parametri out e inout (sostanzialmente un passaggio per riferimento) vengono utilizzate delle apposite classi wrapper dette Holder.
Classi Holder
Le classi Holder sono utilizzate per supportare il passaggio di parametri out e inout. Come si è visto in precedenza dalla compilazione di un'interfaccia IDL viene generata una corrispondente classe <NomeInterfaccia>Holder; l'Holder è generato per ogni tipo utente. Nel package org.omg.CORBA sono forniti gli Holder per tutti i tipi primitivi. Ogni Holder fornisce un costruttore di default che inizializza il contenuto a false, 0, null o null unicode a seconda del tipo.
Ecco per esempio l'Holder del tipo base int
// JAVA
final public class IntHolder
implements org.omg.CORBA.portable.Streamable {
public int value;
public IntHolder() {}
public IntHolder(int initial) {...}
public void _read(
org.omg.CORBA.portable.InputStream is) {...}
public void _write(
org.omg.CORBA.portable.OutputStream os) {...}
public org.omg.CORBA.TypeCode _type() {...}
}
Classi Helper
Per ogni tipo definito dall'utente il processo di compilazione genera una classe Helper con il nome <TipoUtente>Helper. La classe Helper è astratta e fornisce alcuni metodi statici che implementano varie funzionalità per manipolare il tipo associato (lettura e scrittura del tipo da/verso uno stream, lettura del repository id, ecc.).
L'unica funzionalità d'utilizzo comune è fornita dal metodo narrow implementato dall'Helper
// generated Java
package basic;
public class HelloWorldHelper {
//…
public static basic.HelloWorld
narrow(org.omg.CORBA.Object that)
throws org.omg.CORBA.BAD_PARAM {
if (that == null)
return null;
if (that instanceof basic.HelloWorld)
return (basic.HelloWorld) that;
if (!that._is_a(id())) {
throw new org.omg.CORBA.BAD_PARAM();
}
org.omg.CORBA.portable.Delegate dup =
((org.omg.CORBA.portable.ObjectImpl)
that)._get_delegate();
basic.HelloWorld result = new
basic._HelloWorldStub(dup);
return result;
}
}
Il metodo narrow effettua un cast "sicuro" dal generico Object Corba al tipo definito. Grazie ad una serie di controlli ciò che verrà ritornato sarà sicuramente un oggetto del tipo atteso oppure una Exception CORBA BAD_PARAM.
Tipi strutturati
Mediante IDL è possibile dare la definizione di entità che non siano classi o interfacce, ma semplici strutture dati. Il mapping con Java sarà comunque operato mediante classi ed interfacce.
Esistono tre categorie di tipi strutturati: enum, union e struct. Tutti i tipi strutturati sono mappati in Java con una final class fornita degli opportuni campi e costruttori, Helper ed Holder.
L'enum è una lista ordinata di identificatori, la Union è un incrocio tra la Union C ed un'istruzione di switch, la Struct è una struttura dati che consente di raggruppare al suo interno più campi.
// IDL
module basic {
enum EnumType {first, second, third, fourth, fifth};
union UnionType switch (EnumType) {
case first: long win;
case second: short place;
default: boolean other;
};
struct Struttura {
string campoA;
string campoB;
};
};
Nell'esempio Struttura sarà mappata con Helper, Holder e la classe
// generated Java
package basic;
public final class Struttura
implements org.omg.CORBA.portable.IDLEntity {
// instance variables
public String campoA;
public String campoB;
// constructors
public Struttura() { }
public Struttura(String __campoA, String __campoB) {
campoA = __campoA;
campoB = __campoB;
}
}
Sequence ed array
In IDL esistono due collezioni tipizzate di dati: sequence ed array. Entrambe sono mappate su array Java. Le sequence possono avere dimensioni predefinite (bounded) o non predefinite (unbounded).
La compilazione dell'esempio genererà solo Helper ed Holder per ByteSequence e MioArray. All'interno della struttura il tipo ByteSequence sarà trattato come array di byte
// generated Java
package basic;
public final class StrutturaConArray
implements org.omg.CORBA.portable.IDLEntity {
// instance variables
public byte[] campoA;
// constructors
public StrutturaConArray() { }
public StrutturaConArray(byte[] __campoA) {
campoA = __campoA;
}
}
Exception
La definizione di una Exception in IDL non è dissimile da quella di una struct. La signature del metodo che la rilancia utilizza la keyword raises (equivalente del Java throws)
Anche il mapping Java assomiglia a quello di una struct, quindi una classe final con i campi definiti nell'exception ed i costruttori opportuni più i soliti Helper ed Holder
// generated Java
package basic;
public final class MyCustomException
extends org.omg.CORBA.UserException
implements org.omg.CORBA.portable.IDLEntity {
// instance variables
public String reason;
// constructors
public MyCustomException() {
super();
}
public MyCustomException(String __reason) {
super();
reason = __reason;
}
}
Le SystemException CORBA derivano da java.lang.RuntimeException, mentre ogni UserException definita in una IDL specializza java.lang.Exception. Per questa ragione è obbligatorio l'handle or declare su tutte le eccezioni utente, mentre non lo è per tutte le SystemException CORBA (CORBA::MARSHAL, CORBA::OBJECTNOTEXIST, ecc.)
Figura 1 - Gerarchia eccezioni CORBA
Conclusioni
In questo articolo abbiamo presentato il linguaggio IDL e le regole di mapping con Java. Il prossimo mese realizzeremo un primo semplice esempio di applicazione Corba.
Bibliografia
[1] G. Morello - "Corso CORBA I Parte: una breve introduzione", Mokabyte N. 48, Gennaio 2001
MokaByte®
è un marchio registrato da MokaByte s.r.l.
Java®, Jini® e tutti i nomi derivati sono marchi
registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione
anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it