Voir le flux RSS

Gugelhupf

[Tutoriel] CORBA en Java

Noter ce billet
par , 12/06/2016 à 20h23 (1150 Affichages)
Auteur : Gokan EKINCI
Date de 1ère publication : 2015-01-12
Date de mise à jour : 2016-06-19
Licence : CC BY-NC-SA
Source d'origine : http://gokan-ekinci.appspot.com/fr/t...ure/java-corba

Sommaire :
Avant de démarrer ce tutoriel…
Introduction
Définitions
Informations complémentaires
Qu’est-ce que l’IDL ?
Les types IDL/Java
Les modules
Les interfaces, les prototypes de méthodes, les constantes, et l’héritage
Les structures
Les values type
Les alias et les tableaux
Les énumérations
Les exceptions
Les opérations asynchrones
TP CORBA Chapitre 1 : Génération du fichier IDL
TP CORBA Chapitre 2 : Schéma UML
TP CORBA Chapitre 3 : Le modèle POA (partie serveur seulement)
TP CORBA Chapitre 4 : Implémentation (partie serveur)
TP CORBA Chapitre 5 : Implémentation (partie cliente)
TP CORBA Chapitre 6 : Bonus
Les erreurs récurrentes
Pour ou contre CORBA ?




Avant de démarrer ce tutoriel…

Avant de démarrer ce tutoriel :

  • Objectif à la fin de ce tutoriel : connaître l’IDL et savoir manipuler une implémentation CORBA en Java.
  • Ce qu’il faut connaître :
    Le langage Java.
  • Connaissances appréciées mais pas obligatoire :
    Etre à l’aise avec la généricité si vous souhaitez comprendre le code de la partie "Bonus".
    L’architecture SOA (cf : RMI et autres protocoles réseaux de type RPC, les web services SOAP en particulier pour le fichier WSDL).
    Un langage comme PL/SQL, PL/pgSQL, SQL/PSM ou T-SQL pour mieux comprendre le concept des modes IN, OUT et INOUT.
    Le C++ pour la syntaxe de l’IDL.
  • Outil nécessaire : Un ordinateur avec le JDK de Sun/Oracle (le JDK contient une implémentation CORBA).
    Pour télécharger le JDK 8 d’Oracle cliquez ici.
  • Norme CORBA utilisé dans ce cours : CORBA 2.3.1 datant d’octobre 1999 (source).
    Pour consulter l’évolution de la norme CORBA voir ce lien : http://www.omg.org/spec/CORBA/.
    Pour consulter l’évolution de l’implémentation CORBA dans Java voir ce lien : http://docs.oracle.com/javase/8/docs...idl/corba.html




Introduction

Citation Envoyé par Wikipédia -> https://fr.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture
CORBA, acronyme de Common Object Request Broker Architecture, est une architecture logicielle, pour le développement de composants et d’Object Request Broker ou ORB. Ces composants, qui sont assemblés afin de construire des applications complètes, peuvent être écrits dans des langages de programmation distincts, être exécutés dans des processus séparés, voire être déployés sur des machines distinctes.
Pour qu’il y ait une communication entre un client et un serveur CORBA, il faut que le client et le serveur possède chacun 1 ORB : une application client-serveur possède au minimum 2 ORB.

Citation Envoyé par Wikipédia -> https://fr.wikipedia.org/wiki/Object_request_broker
Un ORB est un ensemble de fonctions (classes Java, bibliothèques C++...) qui implémentent un « bus logiciel » par lequel des objets envoient et reçoivent des requêtes et des réponses, de manière transparente et portable : il s'agit de l'activation ou de l'invocation à distance par un objet, d'une méthode d'un autre objet distribué, en pratique les objets invoqués sont souvent des services.
Étudions le schéma suivant :

Les étapes :
  1. Le client CORBA invoque une méthode distante.
  2. Le Stub du client CORBA marshalise l’invocation de la méthode distante en requête CORBA.
  3. La requête CORBA est envoyée à travers le réseau à partir l’ORB du client.
  4. La requête CORBA est réceptionnée par l’ORB du serveur.
  5. Le Skeleton du serveur CORBA démarshalise la requête CORBA.
  6. Le serveur CORBA exécute le service lié à la méthode invoquée.
  7. Le Skeleton du serveur CORBA marshalise la réponse CORBA.
  8. La réponse CORBA est envoyée à travers le réseau à partir de l’ORB du serveur.
  9. La réponse CORBA est réceptionnée par l’ORB du client.
  10. Le Stub du client CORBA démarshalise la réponse CORBA.


Ce que l’on peut remarquer dans ce schéma, c’est qu’il ressemble plus ou moins à celui du protocole HTTP (avec un navigateur qui joue le rôle de client et un serveur web)... donc si on fait l’analogie entre HTTP et CORBA, un client HTTP connaît le serveur grâce à une URL mais comment un client CORBA connaît le serveur CORBA dans ce cas ? La réponse est l’IOR (voir sa définition dans la partie suivante)... pour faire court l’IOR est un objet contenant plusieurs informations permettant d’identifier un "servant" (la méthode distante). Comment obtenir cet IOR ? Il existe 2 moyens standards :
  1. Le serveur stocke l’IOR dans un fichier et le client trouve un moyen de récupérer ce fichier pour lire son contenu. Il est possible de générer un objet proxy à partir de l’IOR grâce à une opération appelée "narrowing".
  2. Le serveur possède un service appelé "NameService", c’est un programme qui tourne en daemon sur le serveur, il est plus connu sous le nom de "orbd" ou "tnameserv". En utilisant le host & port du serveur et un nom attribué au service le client peut récupèrer l’IOR et génère un objet proxy.

Dans notre cas nous utiliserons la deuxième solution car c’est la plus propre.



Définitions
GIOP
(General Inter ORB Protocol)
La spécification du protocole réseau utilisé par CORBA s’appelle GIOP (General Inter ORB Protocol).
  • Une implémentation de GIOP est IIOP (Internet Inter ORB Protocol). IIOP utilise la couche de transport TCP/IP.
  • Une autre implémentation de GIOP est MIOP (Multicast Inter ORB Protocol).
Stub
(Souche en français)
Le Stub est une portion de code coté client qui réalise l’emballage des opérations (requêtes CORBA à envoyer au serveur) et le déballage des résultats (réponse du serveur).
Skeleton
(Squelette en français)
Le Skeleton est une portion de code côté serveur qui réalise le déballage des opérations (requêtes CORBA envoyées par le client) et l’emballage des résultats (réponse à retourner au client).
Servant Le Servant est une instance de la classe d’implémentation du service (coté serveur). La classe d’implémentation devra hériter des classes POA.
Objet Proxy Une objet proxy est unobjet qui est le représentant du servant (coté client)
IOR
(Interoperable Object Reference)
L’IOR est un objet CORBA que s’échangent le client et le serveur afin d’identifier le « servant ». Concrètement l’IOR est une référence créée par le serveur, cela permet au client de localiser le servant à partir d’un host, port, identifiant de méthode.
Narrowing
(méthode narrow(ior) )
Le "narrowing" est une opération consistant à retourner un "Objet Proxy" à partir d’une IOR.
POA
(Portable Object Adapter)
Le POA ou "Adaptateur d’objet" en français, est l'entité coté serveur qui gère les IOR et les servants.
AOM
(Active Object Map)
L’AOM est une table qui enregistre les couples IOR/Servant. L’AOM est géré par le POA.
Bus logiciel CORBA Le bus logiciel CORBA représente l’ensemble des composants logiciels qui permettent à un client et serveur CORBA de communiquer ensemble.
Interopérabilité L'interopérabilité représente la possibilité pour deux composants (des ORBs dans le cas de CORBA) :
  • ...développés avec des langages différents (C++, Java etc),
  • ...utilisant des implémentations différentes (MICO, TAO, omniORB etc),
  • ...exécutés dans des environnements différents (Windows, Linux, Mac etc)

=> de pouvoir travailler ensemble. En gros l’interopérabilité signifie que si un client n’utilise pas la même implémentation que le serveur, ce n’est pas grave, car les 2 entités pourront communiquer sans problème.

Glossaire Oracle pour CORBA (version détaillée ici) :
  • idlj : est un utilitaire inclus dans le JDK pour générer des classes Java à partir d’un IDL.
  • orbd (ou tnameserv) : est un utilitaire inclus dans le JDK, il permet d’initialiser le service de nommage appelé NameService. Il faut lancer cette commande avant de démarrer le programme du serveur.




Informations complémentaires

Ils existent de nombreuses implémentations de CORBA, ces implémentations sont interopérables :


Vous devez bien savoir faire les différences entre :
  • Skeleton et Servant : Le Skeleton est le code généré par idlj, c’est la classe xxxPOA.java (ou xxxPOATie.java si on souhaite appliquer le pattern TIE), son objectif est de démarshaliser les requêtes CORBA envoyées par le Client. Le Servant est une instance de la classe d’implémentation créé par le développeur.
  • Stub et Objet Proxy : Le Stub est le code généré par idlj, c’est la classe _xxxStub.java, son objectif est de marshaliser les requêtes envoyées au Serveur. L’objet Proxy est un wrapper de l’interface IDL retourné par la méthode narrow(ior), c’est l’objet dont on va se servir coté client pour envoyer des requêtes.




Qu’est-ce que l’IDL ?

L’IDL (Interface Definition Language) est un langage dont la syntaxe est proche de celle du C++, le principe d’un fichier IDL est le même qu’un fichier XSD (XML Schema Definition) ou WSDL : générer du code (Java, C++, Python etc).

Le fichier IDL représente un contrat entre le serveur et ses clients : le serveur et ses clients doivent utiliser le même fichier de contrat pour générer leur code.

L’IDL est indépendant du langage d'implémentation utilisé coté client ou serveur. On peut avoir un client en C++ et un serveur en Java par exemple.

Quelques limitations :
  • La surcharge de méthode n’existe pas, il n’est donc pas possible de définir 2 méthodes ayant le même nom même si leur signature (nom et paramètres) sont différents.
  • Il n’existe pas de moyen de représenter les templates ou la généricité avec l’IDL.
  • L’IDL n’est pas sensible à la casse : il n’est donc pas possible d’avoir un nom de paramètre avec une casse différente que son type, de même que pour les noms de module et d’interface.


Exemple d'IDL :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
module fr {
  module ekinci {
    interface CalculationService {
      long factorial(in long num);
    };
  };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
package fr.ekinci;
 
public interface CalculationServiceOperations {
  int factorial(int num);
}
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
package fr.ekinci;
 
public interface CalculationService
  extends CalculationServiceOperations,
  org.omg.CORBA.Object,
  org.omg.CORBA.portable.IDLEntity
{ }



Les types IDL / Java

Les types :
IDL Java (mode in) Java (mode out ou inout)
void (type de retour) void void
boolean boolean org.omg.CORBA.BooleanHolder
char char org.omg.CORBA.CharHolder
wchar char org.omg.CORBA.CharHolder
octet byte org.omg.CORBA.ByteHolder
short short org.omg.CORBA.ShortHolder
unsigned short short org.omg.CORBA.ShortHolder
long int org.omg.CORBA.IntHolder
unsigned long int org.omg.CORBA.IntHolder
long long long org.omg.CORBA.LongHolder
unsigned long long long org.omg.CORBA.LongHolder
float float org.omg.CORBA.FloatHolder
double double org.omg.CORBA.DoubleHolder
long double Incompatibilité en Java Incompatibilité en Java
string java.lang.String org.omg.CORBA.StringHolder
wstring java.lang.String org.omg.CORBA.StringHolder
Object (différent de java.lang.Object ! Sert à représenter les IOR). org.omg.CORBA.Object org.omg.CORBA.ObjectHolder

Exemple de prototype de méthode IDL :
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
void meth1();
long meth2(in long param1);
string meth3(in long param1, out octet param2, inout string param3);
Les mots-clés in, out et inout sont obligatoires pour les paramètres de méthode :
  • in : Passage par copie de valeur.
    Au niveau serveur, on va récupérer le paramètre entré par le client et s’en servir dans l’implémentation du service. Si le serveur affecte une nouvelle valeur à ce paramètre le client n’en sera pas au courant. C’est le mode le plus simple.
  • out : Passage par référence.
    Le serveur ne lit pas la valeur de ce paramètre mais peut y affecter une nouvelle valeur. Le client pourra lire la valeur affectée par le serveur.
  • inout : Passage par référence.
    Le serveur peut lire la valeur de ce paramètre et peut y affecter une nouvelle valeur. Le client pourra lire la valeur affectée par le serveur.




Les modules

L’équivalent du package Java est module en CORBA :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
module fr {
    module ekinci {
        // définir une interface
    };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
package fr.ekinci;
 
// interface Java implémentant `IDLEntity`
A savoir :
  • L’exemple IDL ci-dessus ne fonctionnera pas avec idlj car le programme attend à ce qu’on ajoute au moins une interface, struct, ou enum dans le module.
  • Javadoc de l’interface IDLEntity.




Les interfaces, les prototypes de méthodes, les constantes, et l’héritage
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module fr {
  module ekinci {
 
    interface Parent{
 
      // Prototypes de méthode
      void meth1();
      long meth2(in long param1);
      string meth3(
        in long param1, 
        out octet param2,
        inout string param3
      );
 
      // Constantes 
      const long MAX = 10000;
      const float FACTOR = (10.0 - 6.5) * 3.91;  
    };
 
    // Héritage
    interface Child : Parent{};
 
  };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
package fr.ekinci;
 
public interface ParentOperations 
{
  void meth1 ();
  int meth2 (int param1);
  String meth3 (
    int param1,
    org.omg.CORBA.ByteHolder param2, 
    org.omg.CORBA.StringHolder param3
  );
}
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
package fr.ekinci;
 
public interface Parent 
    extends ParentOperations, 
    org.omg.CORBA.Object,
    org.omg.CORBA.portable.IDLEntity 
{  
  public static final int MAX = (int)(10000);
  public static final float FACTOR = 
   (float)((double)((double)(10.0 - 6.5) * 3.91));
}
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
public interface ChildOperations  
    extends fr.ekinci.ParentOperations
{  }
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
package fr.ekinci;
 
public interface Child
    extends ChildOperations,
    fr.ekinci.Parent,
    org.omg.CORBA.portable.IDLEntity
{  }

A savoir :
  • Une interface ne peut pas contenir d’attribut, seulement des prototypes de méthode et des constantes.
  • La surcharge de méthode n’existe pas, il n’est donc pas possible de définir 2 méthodes ayant le même nom même si leur signature (nom et paramètres) sont différents.
  • Une interface ne peut pas contenir une autre interface (cf : "nested interface" ou "interface imbriquée").


La commande idlj.exe -fall myfile.idl va générer 12 fichiers :
  • Parent.java, ParentHolder.java, ParentHelper.java, ParentOperations.java, ParentPOA.java, _ParentStub.java
  • Child.java, ChildHolder.java, ChildHelper.java, ChildOperations.java, ChildPOA.java, _ChildStub.java

Note : L’interface ChildOperations hérite de ParentOperations, ce sont ces interfaces qui contiennent les prototypes de méthode en Java.



Les structures

Les structures et valuetypes permettent de réaliser des types complexes.

IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
module fr {
  module ekinci {
    struct Classe {
      short a;
      long b;
      double c;
      string d;
    };
  };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package fr.ekinci;
 
public final class Classe 
    implements org.omg.CORBA.portable.IDLEntity {
  public short a = (short)0;
  public int b = (int)0;
  public double c = (double)0;
  public String d = null;
 
  public Classe (){ } // ctor
 
  public Classe (short _a, int _b, double _c, String _d) {
    a = _a;
    b = _b;
    c = _c;
    d = _d;
  } // ctor
 
} // class Classe

A savoir :
  • Une structure ne peut pas contenir de prototype de méthode, seulement des attributs.
  • Une structure ne peut pas hériter d’une autre structure.
  • Une structure ne peut pas contenir de constante.




Les values type

IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
module fr {
  module ekinci {
    valuetype ParentValue {
      public long a;
      string getFoo(in long param1);
    };
 
    valuetype ChildValue : ParentValue {
      private double b;
    };
  };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
package fr.ekinci;
 
public abstract class ParentValue 
implements org.omg.CORBA.portable.StreamableValue {
  public int a = (int)0;
 
  public abstract String getFoo (int param1);
 
  // Voir fichier Java pour le reste
}
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
package fr.ekinci;
 
public abstract class ChildValue 
extends fr.ekinci.ParentValue {
    protected double b = (double)0;
 
    // Voir fichier Java pour le reste
}

A savoir :
  • Les valuetype ont étés introduit après interface et struct.
  • Contrairement aux interfaces et structures, un valuetype peut à la fois contenir des attributs et des prototypes de méthode.
  • Un valuetype peut hériter d’un autre valuetype.
  • Il faut obligatoirement définir un type d’accès aux attributs IDL (private ou public ; protected n’existe pas).
    Une fois le code généré, on remarque que le type d’accès private en IDL devient protected en Java.
  • Il est possible de précéder le mot-clé valuetype de abstract, il faudra alors enlever les attributs ainsi que le type d’accès pour les prototypes de méthode. Un abstract valuetype d'IDL est généré en tant qu’interface Java.


La commande idlj.exe -fall myfile.idl va générer 8 fichiers :
  • ParentValue.java, ParentValueDefaultFactory.java, ParentValueHelper.java, ParentValueHolder.java
  • ChildValue.java, ChildValueDefaultFactory.java, ChildValueHelper.java, ChildValueHolder.java




Les alias et les tableaux

Pour créer un attribut de type tableau dans une struct IDL :
IDL Extrait du code Java généré à partir de l’IDL
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
public int tab[] = null;
/!\ : La taille 10 est gérée dans la classe xxxHelperPour créer un attribut de type tableau sans une limite prédéfini dans une struct IDL il faut utiliser le mot clé sequence :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
sequence<long> integerList;
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
public int integerList[] = null;

Pour utiliser un tableau ou sequence en tant que paramètre dans un prototype de méthode il faut obligatoirement passer par un alias grâce au mot-clé typedef :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
typedef long TabInt[10][10];
TabInt method(in TabInt param);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
int[][] method(int[][] param);
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
typedef sequence<long> TabInt;
TabInt method(in TabInt param);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
int[] method(int[] param);
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
typedef sequence<long> TabInt;
TabInt method(inout TabInt param);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
int[] method(fr.ekinci.TabIntHolder param);
/!\ Attention : Il est possible de placer l’instruction typedef à l’extérieur ou à l’intérieur de l’interface contenant le prototype de méthode avec le paramètre de type tableau ou sequence, cependant cette instruction doit être déclarée avant l’utilisation de l’alias.



Les enumerations

IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
module fr {
  module ekinci {
    enum Toto {
      UN, DEUX, TROIS
    };
  };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package fr.ekinci;
 
public class Toto implements org.omg.CORBA.portable.IDLEntity
{
  private int __value;
  private static int __size = 3;
  private static fr.ekinci.Toto[] __array = 
    new fr.ekinci.Toto [__size];
 
  public static final int _UN = 0;
  public static final fr.ekinci.Toto UN = 
    new fr.ekinci.Toto(_UN);
  public static final int _DEUX = 1;
  public static final fr.ekinci.Toto DEUX = 
    new fr.ekinci.Toto(_DEUX);
  public static final int _TROIS = 2;
  public static final fr.ekinci.Toto TROIS = 
    new fr.ekinci.Toto(_TROIS);
 
  public int value ()
  {
    return __value;
  }
 
  public static fr.ekinci.Toto from_int (int value)
  {
    if (value >= 0 && value < __size)
      return __array[value];
    else
      throw new org.omg.CORBA.BAD_PARAM ();
  }
 
  protected Toto (int value)
  {
    __value = value;
    __array[__value] = this;
  }
} // class Toto

La commande idlj.exe -fall myfile.idl va générer 3 fichiers :
  • Toto.java
  • TotoHolder.java
  • TotoHelper.java




Les exceptions

IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module fr {
  module ekinci {
 
    // Création de l’exception
    exception TransactionException{};
 
 
    // raises IDL = throws Java
    interface DistantTransaction {
      void prepare() 
        raises (TransactionException);
 
      void commit() 
        raises(TransactionException);
 
      void rollback() 
        raises(TransactionException);
    };
 
  };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
package fr.ekinci;
 
public interface DistantTransaction 
    extends DistantTransactionOperations,
    org.omg.CORBA.Object,
    org.omg.CORBA.portable.IDLEntity 
{  } // interface DistantTransaction
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package fr.ekinci;
 
public final class TransactionException 
    extends org.omg.CORBA.UserException {
 
  public TransactionException () {
    super(TransactionExceptionHelper.id());
  } // ctor
 
  public TransactionException (String $reason) {
    super(TransactionExceptionHelper.id() + "  " + $reason);
  } // ctor
 
} // class TransactionException

A savoir :


La commande idlj -fall myfile.idl va générer 9 fichiers :
  • DistantTransaction.java, DistantTransactionHolder.java, DistantTransactionHelper.java, DistantTransactionOperations.java, DistantTransactionPOA.java, _DistantTransactionStub.java
  • TransactionException.java, TransactionExceptionHolder.java, TransactionExceptionHelper.java




Les opérations asynchrones

Les méthodes CORBA sont exécutées de manière synchrone (comme pour les méthodes RMI en Java), cela signifie que si le serveur prend 2 secondes pour exécuter une méthode, le client devra attendre ces 2 secondes + le temps réseau que peut prendre l’échange de requête entre le client et le serveur. Cependant il existe un mot-clé oneway permettant de rendre une méthode asynchrone : le client n’attend pas que le serveur ait terminé d’exécuter la méthode oneway et passe directement à l’instruction qui suit.

Pour mettre en place une méthode asynchrone il faut respecter quelques contraintes :
  • Une méthode oneway ne peut pas avoir de valeur de retour (utiliser void).
  • Une méthode oneway ne peut qu’accepter des paramètres en mode in.
  • Une méthode oneway ne peut pas lancer d’exception.


Exemple :
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
module fr {
  module ekinci {
    interface Asynchronous {
 
      oneway void farewell();
 
    };
  };
};

Liste des fichiers générés suite à l’exécution de idlj -fall myfile.idl : _AsynchronousStub.java, Asynchronous.java, AsynchronousHelper.java, AsynchronousHolder.java, AsynchronousOperations.java, AsynchronousPOA.java

/!\ Attention : Si vous êtes familier avec les environnements multithread et avez connaissance des problèmes de concurrences, vous savez que les méthodes asynchrones sont à manipuler avec prudence.



TP CORBA Chapitre 1 : Génération du fichier IDL

Pour générer des classes Java à partir du fichier IDL, nous allons utiliser le programme "idlj" présent dans le JDK, celui-ci porte l’extension ".exe" sous Windows. Ce programme se trouve dans le répertoire d’installation du JDK "jdkxxx/bin/".

Nous allons utiliser la commande suivante : idlj -fall <Insérer nom fichier idl> exemple : idlj -fall calcul.idlAvec le paramètre -fall on va générer plusieurs fichiers de code Java pour la partie client et serveur.
Le nombre de fichier généré dépendra des éléments utilisés dans le fichier idl. Par exemple pour chaque énumération (enum) ajoutée dans le fichier idl, idlj va générer 3 fichiers Java supplémentaires.

Autrement :
  • La commande idlj calcul.idl est équivalente à idlj -fclient calcul.idl et permet de générer uniquement les fichiers liés à la partie cliente.
  • La commande idlj -fserver calcul.idl permet de générer uniquement les fichiers liés à la partie serveur.
  • La commande idlj -fclient -fserver calcul.idl est équivalente à la commande idlj -fall calcul.idl et permet de générer tous les fichiers (partie cliente et serveur).

Pour plus d’information sur le programme idlj, lire la documentation Sun/Oracle.

Créez un fichier IDL avec le contenu suivant :

IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
module fr {
  module ekinci {
    interface CalculationService {
      long factorial(in long num); 
    }; 
  };
};
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
package fr.ekinci;
 
public interface CalculationService
    extends CalculationServiceOperations, 
    org.omg.CORBA.Object, 
    org.omg.CORBA.portable.IDLEntity 
{  } // interface CalculationService

6 fichiers seront générés si on exécute la commande suivante idlj -fall calcul.idl :
Fichier Description
CalculationServicePOA.java Ce fichier contient une classe abstraite représentant le skeleton du serveur
_CalculationServiceStub.java Ce fichier contient une classe représentant le stub du client
CalculationService.java Ce fichier contient notre interface Java CalculationService. L’interface CalculationService hérite de CalculationServiceOperations, org.omg.CORBA.Object et org.omg.CORBA.portable.IDLEntity
CalculationServiceHelper.java Ce fichier contient une classe abstraite permettant de faire du narrowing et de gérer le type org.omg.CORBA.Any
CalculationServiceHolder.java Ce fichier contient une classe final permettant de gérer les paramètres de méthode en mode out ou inout
CalculationServiceOperations.java Cette interface contient les prototypes de méthode de CalculationService, dans notre cas il contient juste la méthode factorial()



TP CORBA Chapitre 2 : Schéma UML

Voici un schéma UML pour avoir une meilleure vision sur la hiérarchie des classes générées :

A savoir :
  • CalculationServiceImpl a été créée à la main et n’a pas été généré avec idlj. Si on décide d’utiliser le "Inheritance Model", CalculationServiceImpl doit hériter de CalculationServicePOA; si on décide d’utiliser le "Tie Delegation Model", CalculationServiceImpl ne doit pas hériter de CalculationServicePOA mais doit implémenter l'interface CalculationServiceOperations (voir le chapitre suivant pour comprendre ces modèles).
  • La classe CalculationServicePOATie n'est générée que si la commande suivante est exécutée : idlj -fallTIE calcul.idl. Cette classe n'est utile que si on souhaite appliquer le "Tie Delegation Model".




TP CORBA Chapitre 3 : Le modèle POA (partie serveur seulement)

Il existe 2 variantes d’implémentation pour le modèle POA pour la partie serveur :
  • L’approche par héritage (The Inheritance Model)
  • L’approche par délégation (The Tie Delegation Model)


La différence entre ces 2 modèles ?
Si on choisit le Inheritance Model, la classe d’implémentation doit obligatoirement hériter de la classe CalculationServicePOA, tandis que si on choisit le Tie Delegation Model, la classe d’implémentation doit juste implémenter l’interface CalculationServiceOperations. Comme il n’y pas d’héritage multiple Java, le Tie Delegation Model sert à libérer un emplacement pour que la classe d’implémentation puisse hériter d’une autre classe, cependant ce modèle possède aussi un inconvénient : une méthode supplémentaire est appelé à chaque appel de méthode distante.

Citation Envoyé par Sun/Oracle -> http://docs.oracle.com/javase/7/docs/technotes/guides/idl/jidlTieServer.html
You might want to use the Tie model instead of the typical Inheritance model if your implementation must inherit from some other implementation. Java allows any number of interface inheritance, but there is only one slot for class inheritance. If you use the inheritance model, that slot is used up. By using the Tie Model, that slot is freed up for your own use. The drawback is that it introduces a level of indirection: one extra method call occurs when invoking a method.
/!\ : L’héritage multiple n’a jamais été une solution incontournable, il est tout à fait possible d’utiliser un design pattern de comportement avec la composition pour s’en passer.

La différence entre ces 2 modèles de POA au niveau de l’implémentation du serveur est relativement faible, et se résume à 2 lignes de codes chacune :
The Inheritance Model The Tie Delegation Model
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
org.omg.CORBA.Object ref = rootpoa.servant_to_reference(calculImpl);
 
CalculationService href = CalculationServiceHelper.narrow(ref);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
CalculationServicePOATie tie = new CalculationServicePOATie(calculImpl, rootpoa);
 
CalculationService href = tie._this(orb);

J'utiliserais le Inheritance Model dans le prochain chapitre.

Activation implicite du servant :
  • Dans l’approche par héritage (Inheritance Model), c’est la méthode servant_to_reference(servant) qui active implicitement notre servant.
  • Dans l’approche par délégation (Tie Delegation Model), c’est la méthode _this(orb) qui active implicitement notre servant.


Les classes utiles pour :
Le client (option -fclient) Le serveur (option -fserver)
CalculationService, CalculationServiceOperations, CalculationServiceHelper, CalculationServiceHolder, _CalculationServiceStub CalculationService, CalculationServiceOperations, CalculationServicePOA

/!\ Attention : Si on utilise le Inheritance Model le serveur nécessite l’utilisation de la classe CalculationServiceHelper, si on utilise le Tie Delegation Model le serveur nécessite l’utilisation de la classe CalculationServiceHelper et CalculationServicePOATie, or la commande idlj -fserver calcul.idl ne génère ni CalculationServiceHelper ni CalculationServicePOATie, il faudra au choix selon le modèle choisit :
  • The Inheritance Model : coté serveur, exécuter la commande idlj -fall calcul.idl pour générer CalculationServiceHelper.
  • The Tie Delegation Model : coté serveur, exécuter la commande idlj -fall calcul.idl pour générer CalculationServiceHelper puis idlj -fallTIE calcul.idl pour générer CalculationServicePOATie.


/!\ : Il existe un autre modèle appelé BOA, qui est l’ancêtre du modèle POA pour implémenter le code du serveur CORBA, bien que dépréciée, un tutoriel sur ce modèle appelé "ImplBase" est toujours disponible pour ceux qui utilisent Java 1.3 (lien).



TP CORBA Chapitre 4 : Implémentation (partie serveur)

I. Soit l’IDL créée dans le chapitre 1 du TP :

Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
module fr {
  module ekinci {
    interface CalculationService {
      long factorial(in long n);
    };
  };
};


II. Générer les fichiers nécessaires avec le programme idlj

Pour utiliser le Inheritance Model, exécuter la commande suivante idlj -fall calcul.idl.
Pour utiliser le Tie Delegation Model, exécuter la commande suivante idlj -fall calcul.idl, puis la commande suivante idlj -fallTIE calcul.idl.

La raison pour laquelle il faut exécuter la 2 commandes pour le Tie Delegation Model :
Citation Envoyé par Sun/Oracle -> http://docs.oracle.com/javase/7/docs/technotes/tools/share/idlj.html#tie
Because it is not possible to generate ties and skeletons at the same time, they must be generated separately.
/!\ : Si vous ne souhaitez pas utiliser le Tie Delegation Model, la seconde commande n’est bien évidemment pas nécessaire.


III. Créer la classe d’implémentation CalculationServiceImpl.java

Le fichier xxxPOA.java (ou xxxPOATie.java pour une implémentation selon le pattern Tie) représente le skeleton du serveur.

Nous allons créer la classe d'implémentation CalculationServiceImpl qui hérite de CalculationServicePOA et implémente toutes les méthodes définies dans l'interface CalculationServiceOperations :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import fr.ekinci.CalculationServicePOA;
 
public class CalculationServiceImpl extends CalculationServicePOA {
 
  @Override
  public int factorial(int n){
    if (n < 2) {
      return 1;
    }
 
    int c = n;
    while(c != 1){
      n *= --c;
    }
 
    return n; 
  }
}

/!\ Attention : Vous remarquerez que pour faire simple notre implémentation de factorielle ne gère pas les cas où n est inférieur à zéro et retourne 1.


IV. Créer une classe MainServer pour l’initialisation du serveur

Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import org.omg.CosNaming.*;
import fr.ekinci.*;
 
 
/**
 * Classe pour lancer le programme du serveur
 * @author Gokan EKINCI
 */
public class MainServer {
    public static void main(String args[]){
        try {
            // Initialisation de l'ORB
            ORB orb = ORB.init(args, null);
 
            // Récupérer la référence du RootPOA et activer le POAManager
            POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
            rootpoa.the_POAManager().activate();
 
            // Créer un servant (instance de classe d'implémentation) et l'enregistrer avec l'ORB
            CalculationServiceImpl calculImpl = new CalculationServiceImpl();
 
 
            /* *** DEBUT INHERITANCE MODEL (vous pouvez vous référer aux chapitres précédents pour utiliser le modèle Tie Delegation Model) *** */
 
            // Récupérer une référence du servant
            org.omg.CORBA.Object servantRef = rootpoa.servant_to_reference(calculImpl);
 
            CalculationService service = CalculationServiceHelper.narrow(servantRef);
 
            /* *** FIN INHERITANCE MODEL *** */
 
            // Récupérer la référence du service de nommage
            org.omg.CORBA.Object nsRef = orb.resolve_initial_references("NameService");
            NamingContextExt nce = NamingContextExtHelper.narrow(nsRef);
 
            // Créer un nom pour le service et ajouter le service
            String serviceName = "MathServices";
            NameComponent nc[] = nce.to_name(serviceName);
            nce.rebind(nc, service);
 
            // Démarrer le service et attendre les requêtes des clients
            System.out.println("On traite les requêtes des clients ...");
            orb.run(); // En attente de nouveaux clients CORBA
 
        } catch (Exception e){
            System.err.println(e);
        }
    }
}


V. Démarrer le serveur : Lancer orbd (processus daemon)

Ce programme se trouve dans le même répertoire que le programme idlj, soit "/jdkxxx/bin".

Commande pour exécuter ordb :
OS Ligne de commande
Linux orbd -ORBInitialPort 1050 -ORBInitialHost localhost&
Windows start orbd -ORBInitialPort 1050 -ORBInitialHost localhost

A savoir :
  • En CORBA, le numéro de port serveur par défaut est 1050 (UDP & TCP | Corba Management Agent source).
    Dans notre exemple, le service de nommage va utiliser le port 1050 et fonctionner sous notre machine (cf: localhost). Dans un environnement de production il est déconseillé d’utiliser un port par défaut, il est conseillé d’utiliser un numéro de port non-reservé (donc supérieur à 1024). En effet, si un pirate souhaite exploiter une faille connu d’une technologie, la première chose qu’il fera sera de s’attaquer à son port par défaut (ex : 1099 pour RMI, 3306 pour MySQL, 5432 pour PostgreSQL, 1433 pour SQL Server etc).
  • Pour les utilisateurs Windows : Le port peut être utilisé par une autre instance d’orbd, utilisez la commande netstat -na pour en savoir plus. Pour arrêter orbd il suffira de quitter la console avec le raccourci Ctrl+C.
  • Pour les utilisateurs Linux : Le port peut être déjà utilisé par une autre instance d’orbd, faite un ps aux pour le voir, puis récupérer son PID pour terminer le processus avec un kill.


Aperçu sous Windows :


Un message d’avertissement Windows peut alors se lancer, autoriser l’accès :


Une nouvelle console se lance automatiquement :

Rappel : Pour quitter ordb faire Ctrl+C (si vous quittez les clients ne pourront plus accéder au service).


VI. Démarrer le programme serveur MainServer

Démarrer le programme du serveur avec la commande suivante s’il s’agit d’un Runnable JAR :
start java -jar MainServer.jar -ORBInitialPort 1050 -ORBInitialHost localhost

Si vous lancez le programme MainServer à partir de l’IDE Eclipse, allez dans Run Configurations… > Onglet Arguments > Program Arguments et copier/coller ceci :
-ORBInitialPort 1050 -ORBInitialHost localhost



TP CORBA Chapitre 5 : Implémentation (partie cliente)

Créer une classe MainClient pour l’initialisation du client :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import fr.ekinci.*;


/**
 * Classe pour lancer le programme du client
 * @author Gokan EKINCI
 */
public class MainClient {
    public static void main(String args[]){
        try {
            // Initialisation de l'ORB
            ORB orb = ORB.init(args, null);

            // Récupérer la référence du service de nommage
            org.omg.CORBA.Object nsRef = orb.resolve_initial_references("NameService");
            NamingContextExt nce = NamingContextExtHelper.narrow(nsRef);

            // Générer un objet proxy
            String serviceName = "MathServices";
            CalculationService service = CalculationServiceHelper.narrow(nce.resolve_str(serviceName));

            System.out.println("Réponse du serveur : " + service.factorial(5)); // 120
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Lancez le programme client à partir de votre IDE, OU BIEN compilez le programme avec javac et lancer le programme java, OU BIEN utilisez la commande suivante si vous avez transformé votre projet en JAR :
java -jar MainClient.jar -ORBInitialPort 1050 -ORBInitialHost localhost

Rappel : Il est nécessaire de lancer le programme Serveur (MainServer) avant le programme Client (MainClient).



TP CORBA Chapitre 6 : Bonus

Dans les chapitres précédents nous avons vu à quel point le code d’une implémentation CORBA peut-être long à écrire... et ce procédé fastidieux sera toujours aussi répétitif. Si la mise en place du code n’était pas aussi difficile CORBA n’aurait pas perdu sa popularité, qui sait ?

Dans cette partie nous allons réaliser le même exercice avec seulement quelques instructions.

Importer le projet corba-wrapper (lien Github) grâce à Maven (ou autres outils de gestion de dépendances) :
Code XML : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
<dependency>
    <groupId>com.github.gokan-ekinci</groupId>
    <artifactId>corba-wrapper</artifactId>
    <version>1.0</version>
</dependency>

Soit la classe CorbaServer :

Créez un projet serveur et utilisez la classe CorbaServer :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
import fr.ekinci.corbawrapper.CorbaServer;
 
public class MainServer {
    public static void main(String args[]) throws Exception {
        CorbaServer server = new CorbaServer("127.0.0.1", 1050); 
 
        // Ajouter un service       
        server.addService("MathServices", new CalculationServiceImpl(), CalculationServiceHelper.class); 
 
        // Démarrer le service
        server.run();                                                          
    }
}
/!\ Attention : CalculationServiceImpl hérite de CalculationServicePOA (cf : utilisez le Inheritance Model, PAS le Tie Delegation Model).

Soit la classe CorbaClient :
Nom : 1453505020-uml-corbaclient.png
Affichages : 249
Taille : 3,8 Ko

Créez un projet client et utilisez la classe CorbaClient :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
import fr.ekinci.corbawrapper.CorbaClient;
 
public class MainClient {
    public static void main(String args[]) throws Exception {
        CorbaClient client = new CorbaClient("127.0.0.1", 1050);
 
        // Récupérer l'objet proxy    
        CalculationService calcul = client.<CalculationService, CalculationServiceHelper>lookup("MathServices", CalculationServiceHelper.class);
 
        // Invoquer des méthodes distantes 
        System.out.println("Réponse du serveur : " + calcul.factorial(5)); // 120
    }
}
Lancer orbd en premier, puis le projet du serveur, puis le projet du client, le résultat obtenu sera le même que dans les chapitres précédents. Simple à mettre en place, n’est-ce pas ?



Les erreurs récurrentes

Il y a fort à parier que le programme client/serveur CORBA que nous avons mis en place fonctionne en localhost sans aucun problème, mais ce ne sera pas forcément le cas entre 2 machines distinctes :
  • Un problème de firewall ou routeur : CORBA utilise un protocole adapté au LAN (Local Area Network). Les firewalls ou routeurs n’aiment pas forcément que 2 entités qui communiquent ensemble changent dynamiquement leur numéro de port... mais c’est le comportement par défaut des protocoles utilisés par CORBA et RMI. Il existe des solutions pour fixer le numéro de port mais nous ne les verrons pas dans ce tutoriel.
  • Si vous rencontrez ce type d’exception au lancement de votre programme client sous Linux :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    WARNING: "IOP00410201: (COMMFAILURE) Connection failure: socketType:
    IIOPCLEAR_TEXT; hostname: 127.0.0.1;
    ... il suffit d’aller sur la machine du serveur et de remplacer l’adresse du localhost par l’adresse IP réelle de la machine dans le fichier /etc/hosts, enfin redémarrez votre machine pour que les modifications soient prises en compte. Notez que ce type de problème est aussi rencontré par ceux qui font du RMI sous Linux, la solution au problème est identique.




Conclusion : pour ou contre CORBA ?

Avantages Inconvénients
CORBA utilise un protocole binaire (GIOP/IIOP), de ce fait c’est un système plus performant (léger et rapide) que les protocoles basé sur les textes (ex : les web services, cf benchmark lire §3.4). A l’heure où j’écris ce tutoriel, la dernière spécification de la norme CORBA est 3.3 (novembre 2012), mais la plupart des dernières implémentations dans les langages informatiques reposent sur la norme 2.3.1 (octobre 1999). A l’époque CORBA était considéré comme une bonne alternative au DCOM de Microsoft, mais depuis d’autres technologies comme les services Web ont supplantés CORBA.
Contrairement à RMI, CORBA a été conçu pour être une solution hétérogène, il est possible d’avoir un serveur en Java et un client en C++ par exemple. Contrairement aux services Web qui utilisent le protocole HTTP adapté au WAN (Wide Area Network), CORBA peut être bloqué par les pare-feu à cause du numéro de port qui change dynamiquement par le serveur CORBA. On limitera l’utilisation de CORBA au LAN.
La spécification 1.0 de CORBA date d’août 1991, c’est une norme mature. La volonté de l’OMG est de rendre CORBA hétérogène, cependant le fichier IDL peut contenir des incompatibilités avec les langages d'implémentations (Java, C++ etc). Un même IDL générant du C++ peut rencontrer des problèmes pour générer du Java par exemple (ex : impossible de retranscrire le type long double IDL en Java).
Ecrire le code d’une implémentation client/serveur CORBA est très long, très répétitif, très "old-school", mais nous avons vu qu’il suffit d’un simple wrapper comme le projet corba-wrapper pour outrepasser ces difficultés.

Aujourd’hui il existe un autre projet open source concurrent de CORBA : gRPC. Ce dernier est un protocole créé par Google, interopérable, possède une implémentation officielle pour de nombreux langages (C++, Java, Python, C# etc), protocole binaire (HTTP/2)... son défaut ? Qu’il ne soit pas assez connu pour le moment.
Aimeriez-vous avoir un tutoriel gRPC ? N’hésitez pas à me le dire dans les commentaires.

Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog Viadeo Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog Twitter Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog Google Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog Facebook Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog Digg Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog Delicious Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog MySpace Envoyer le billet « [Tutoriel] CORBA en Java » dans le blog Yahoo

Mis à jour 25/07/2016 à 10h22 par Gugelhupf (Correction partie TP4 partie 6)

Catégories
Java , Programmation

Commentaires

  1. Avatar de Malick
    • |
    • permalink
    Gugelhupf

    Super ton article. Toutefois il manque ce chapitre :

    TP CORBA Chapitre 5 : Implémentation (partie cliente)
  2. Avatar de Gugelhupf
    • |
    • permalink
    Bonsoir Malick,

    Un grand merci pour ton aide. J'avais oublié d'ajouter cette partie, je viens de le faire à l'instant

    Cordialement,