Application de commerce électronique
Etape 3 - Application bancaire persistante
-
F. Boyer, S. Chassande, D. Feliot, S. Krakowiak, D. Donsez
-
Projet de DESS-GI option SRR et RICM3 option SR
-
Année Universitaire 2001-2002
-
Université Joseph Fourier, Grenoble
1. Motivations
2. Architecture globale
3. Le POM (Persistent Object
Adapter)
4. La classe CorbaPersistentObject
5. Le BOA
(Basic Object Adaptor)
6.
Implications sur le modele de programmation Corba
7. L'application
bancaire persistente
1. Motivations
Chaque serveur CORBA utilisé dans l'application bancaire (banque,
agence) rend accessible à distance un ensemble d'objets (banques,
agences, comptes, clients). Ces objets ne sont accessibles que pendant
la durée de vie du serveur qui les gère. Ils sont perdus
en cas d'arrêt ou de panne du serveur. Un client ayant conservé
une référence distribuée vers l'un de ces objets ne
peut plus y accéder.
On souhaite pallier à ce problème en réalisant
des serveurs d'objets persistants. Un objet persistant est
un objet qui survit à l'arrêt du serveur dans lequel il a
été créé. Pour l'application GICOM, on cherchera
a assurer que, lorsqu'un serveur est arrêté puis relancé,
alors les objets persistants qu'il gérait sont à nouveau
accessibles.
En outre, on souhaite rendre persistantes les références
distribuées (dans la mesure où elles référencent
des objets persistants), c'est à dire que lorsqu'un serveur est
arrêté puis relancé, alors les objets persistants qu'il
gérait sont à nouveau accessibles au travers des mêmes
références distribuées. Cette fonctionnalité
facilite grandement l'écriture des programmes clients.
Les propriétés suivantes caractérisent plus précisément
le service à réaliser :
-
Les objets persistants de GICOM possèdent un état stable
qui correspond par exemple à une sauvegarde sur disque. Lorsqu'un
serveur est relancé après un arrêt, ses objets persistants
sont réinitialisés avec le dernier état dans lequel
ils ont été sauvegardés avant d'être reconnectés
au monde CORBA.
-
Les sauvegardes des objets persistants sont décidées par
l'application (et non par le service de persistance).
-
La sauvegarde d'un objet est effectuée de manière atomique
: en cas de panne survenant lors de la sauvegarde, l'objet retrouve
un état cohérent (soit son ancien état , soit le nouvel
état sauvegardé).
2. Architecture globale
La gestion de la persistance intervient à deux niveaux. Il faut
d'une part disposer d'un service de stockage des états stables des
objets dans une mémoire permanente. Nous appelons ce service POM
(Persistent Object Manager). Ses principales fonctions sont les suivantes.
-
chargement d'un objet depuis son image persistente (load),
-
déchargement d'un objet vers son image persistente, de manière
atomique (save). Pour mettre en oeuvre une sauvegarde atomique,
on réfléchira aux différents mécanismes utilisables
(utilisation de fichiers .bak, utilisation d'un journal dans lequel
l'état de l'objet est enregistré avant de mettre à
jour son état stable).
D'autre part, un serveur Corba (et plus précisément
le POA concerné) doit faire appel au POM lorsqu'il recoit une requête
concernant un objet persistant qui n'est pas actif (par défaut,
la réception d'une requête concernant un objet non actif génère
une erreur). Il faut plus précisément :
-
recréer l'objet et le réinitialiser
depuis son image persistente,
-
recréer son servant (lorsqu'un modèle
par délégation est utilisé).
Pour ce faire, nous utiliserons les facilités fournies par Corba
2.2 permettant de configurer et d'adapter les POA en fonction des contraintes
spécifiques des applications.
Figure 1. Architecture générale du service de persistance
2.1. Le POA (Portable Object Adapter)
Le POA est la partie d'un serveur CORBA qui est chargée
de :
-
réceptionner une requête entrante sur
un objet distribué (soit O)
-
récupérer l'adresse du servant de cet
objet
-
réaliser la requête
-
retourner le résultat
Dans Corba 2, un serveur peut mettre
en oeuvre plusieurs POA qui gèrent de manière plus ou moins
spécifiques leurs objets distribués. Lors du lancement d'un
serveur, nous avons vu (étape 2) qu'un POA par défaut (appelé
le "root POA") est automatiquement disponible. Le serveur peut demander
la création d'un ou plusieurs POA, qui seront des fils (directs
ou indirects) du POA racine.
La création d'un POA implique
de configurer celui-ci au moyen de règles. Il existe plusieurs catégories
de règles, permettant d'agir sur :
-
l'association d'identificateurs aux
objets distribués
-
la composition des références
d'objets
-
l'ordonnancement des requêtes
-
le traitement des requêtes
-
etc
Toute règle possède un
nom symbolique et un ensemble de valeurs, dont une est affectée
par défaut lors de la création d'un POA. Les règles
qui vont nous intéresser pour la gestion de la persistence sont
les suivantes :
-
La règle IdAssignmentPolicy
qui peut prendre pour valeur SYSTEM_ID (c'est le POA qui affecte un ObjectId
aux objets distribués), ou bien USER_ID (c'est à l'application
d'affecter un ObjectId aux objets distribués). La valeur par défaut
est SYSTEM_ID.
//Creation d'un
tableau de Policies
org.omg.CORBA.Policy [] policies
= new org.omg.CORBA.Policy[4];
// Policies for the PersistentPOA
policies[0] = rootPOA.create_id_assignment_policy(
org.omg.PortableServer.IdAssignmentPolicyValue.USER_ID); |
-
La règle LifespanPolicy,
qui peut prendre pour valeur TRANSIENT (les références d'objets
distribués contiennent des informations non persistentes, dans le
sens où un servant n'aura pas deux fois la même référence
si on relance un serveur), ou bien PERSISTENT (une référence
d'objet distribuée reste valide dès lors qu'il existe un
servant pour l'objet identifié par la référence).
La valeur par défaut est TRANSIENT.
policies[3] = rootPOA.create_lifespan_policy(org.omg.PortableServer.LifespanPolicyValue.PERSISTENT); |
-
La règle RequestProcessingPolicy,
qui peut prendre pour valeur USE_ACTIVE_OBJECT_MAP_ONLY, ou USE_DEFAULT_SERVANT,
ou bien USE_SERVANT_MANAGER. Dans tous les cas, le servant concerné
par une requête est recherché dans une table des objets actifs
(AOM, Active Object Map), qui contient les servants qui ont été
activés auprès du POA courant. La règle RequestProcessingPolicy
intervient lorsque le servant concerné par une réquête
n'est pas présent. Si la valeur de la propriété
RequestProcessingPolicy est USE_ACTIVE_OBJECT_MAP_ONLY alors une exception
est levée. Si la valeur de la propriété RequestProcessingPolicy
est USE_DEFAULT_SERVANT alors la requête est transmise à un
servant par défaut (qui a été associé au POA
préalablement par l'application). Enfin, si la valeur de la propriété
RequestProcessingPolicy est USE_SERVANT_MANAGER alors le POA fait appel
à un service externe (fourni par l'application) appelé ServantManager.
Ce service doit trouver ou créer un servant pour traiter la requête
en cours. La valeur par défaut de la règle Request Processing
Policy est USE_ACTIVE_OBJECT_MAP_ONLY.
// use my servant
manager
policies[1] = rootPOA.create_request_processing_policy(org.omg.PortableServer.RequestProcessingPolicyValue.USE_SERVANT_MANAGER); |
Pour mettre en oeuvre des objets distribués
persistents, nous allons activer ces objets auprès d'un POA spécifique
(que nous appellerons PPOA, Persistent POA). Ce POA possèdera les
règles suivantes :
-
IdAssignmentPolicy = USER_ID,
pour pouvoir associer des identificateurs uniques aux objets persistents.
L'unicité doit être garantie même en cas de relancement
d'un serveur.
-
LifespanPolicy = PERSISTENT,
pour garantir qu'une référence vers un objet persistent reste
valide même si son serveur est arrêté puis relancé.
-
RequestProcessingPolicy = USE_SERVANT_MANAGER,
pour pouvoir recharger les objets depuis leur image persistente lorsque
cela est nécessaire.
Lorsque la règle RequestProcessingPolicy
est affectée à la valeur USE_SERVANT_MANAGER, il est
possible d'associer deux types de ServantManagers au POA courant : un ServantActivator
ou un ServantLocator. C'est la règle ServantRetentionPolicy qui
détermine le type du ServantManager utilisé. Si cette règle
vaut RETAIN, alors les servants sont ajoutés dans l'AOM au moment
de leur activation. Lorsqu'un servant n'est pas trouvé dans l'AOM,
l'appel au ServantManager engendre automatiquement l'ajout du servant crée
dans l'AOM. Ceci n'est pas vrai dans le cas où la règle vaut
NON_RETAIN.
// call my servant
manager only for servant activation (not for servant location)
policies[2] = rootPOA.create_servant_retention_policy(org.omg.PortableServer.ServantRetentionPolicyValue.RETAIN); |
Voici un extrait du code permettant
de créer un PPOA tel que nous le proposons dans un serveur Corba.
...
// Policies for the PersistentPOA
org.omg.CORBA.Policy
[] policies = new org.omg.CORBA.Policy[n];
.....Definition des policies ..........
policies[0] = ......
policies[n] = ......
try {
persistentPOA
= rootPOA.create_POA("PersistentPOA", rootPOA.the_POAManager(),
policies);
} catch(Exception ex)
{
System.out.println("Server
: can't create 'PersistentPOA' " + ex);
}
// Create and use my
servant activator
try {
org.omg.PortableServer.ServantActivator
activator = new
PersistentActivator()._this(orb);
persistentPOA.set_servant_manager(activator);
} catch(Exception ex)
{
System.err.println("Server
: can't use 'PersistentActivator' " + ex);
}
... |
2.2 Le POM(Persistent
Object Manager)
Le POM est un service qui fournit principalement les méthodes
suivantes :
-
load (pid) -> obj : chargement d'un objet persistent depuis son état
stable identifié par l'ObjectId pid,
-
store(pid, obj) : sauvegarde de l'état stable de l'objet persistent
obj
identifié par pid.
-
register(obj) -> pid : enregistrement de l'objet obj auprès
du POM (affectation d'un ObjectId).
La charge de créer des ObjectId uniques au sein d'un serveur
Corba peut être attribuée au POM, puisque celui-ci manipule
des informations persistantes (la connaissance des ObjectId dejà
alloués doit être persistante).
Il faut toutefois noter que le format des références distribuées
n'est pas sérialisable, auquel cas des transformations devront être
appliquées pour pouvoir conserver ces références dans
des objets persistants.
Composition d'une référence
distribuée
3. Mise en oeuvre des objets persistants
Toutes les classes et interfaces requises pour pouvoir utiliser des objets
persistents feront partie d'un même package Java appelé
Persistence.
Ce package définit les éléments suivants.
-
l'interface PersistentObject
-
la classe PersistentObjectImpl
-
l'interface PersistentObjectManager
-
la classe PersistentObjectManagerImpl
3.1 L'interface PersistentObject et la classe PersistentObjectImpl
Etant données les hypothèses fixées en 1, tout
objet persistent doit satisfaire une interface minimale, permettant entres
autres à l'application de décider des moments auxquels auront
lieu une sauvegarde de l'objet dans la mémoire persistente. Par
ailleurs, l'implémentation des méthodes permettant de gérer
la persistence de ces objets relève du service de persistence, et
non de l'application (nous verrons que certaines méthodes devront
toutefois être définies par l'applicatif).
Nous proposons qu'un objet persistant implémente l'interface
étende la classe Persistence.PersistentObjectImpl. Cette
classe implémente l'interface Persistence.PersistentObject
qui définit les méthodes suivantes.
-
Object getRef (): retourne la référence
distribuée de cet objet. Cette méthode active l'objet auprès
du POA si cela n'a pas encore été fait. Le choix du
POA auprès duquel a lieu l'activation d'un objet persistent peut
être fait au moment de la création de l'objet persistent.
-
save() : réalise une sauvegarde atomique de l'objet.
Pour récupérer l'état courant de l'objet, cette classe
utilise les méthodes
de sérialisation / désérialisation fournies par
Java. Cette méthode doit être appelée par l'application,
lorsqu'il est nécessaire de sauvegarder l'état courant d'un
objet. L'appel ne peut avoir lieu que dans l'implémentation
d'un objet distribué, à moins
de rendre cette méthode disponible au niveau de l'interface IDL
de l'objet distribué.
-
Servant createServant (): crée un servant
pour l'objet persistent. Cette méthode est appelée par le
POA après avoir chargé l'objet.
-
void init() : cette méthode est appelée
par le POA après avoir rechargé l'objet. L'application peut
surcharger cette méthode pour effectuer certains traitements (réinitialisation
des variables transient par exemple).
-
void destroy() : cette méthode désactive
l'objet et le détruit de la mémoire persistente. Cette
méthode doit être appelée par l'application.
package Persistence;
...
public interface PersistentObject
extends Serializable {
public org.omg.CORBA.Object
getRef ()
public org.omg.PortableServer.Servant createServant()
public void save();
public void init();
public
void destroy();
...
}
public abstract classPersistentObjectImplimplements
PersistentObject {
...
} |
3.2 L'interface PersistentObjectManager et la classe PersistentObjectManagerImpl
Le POM est un service qui doit être disponible dans tout serveur
Corba d'objets persistants. Dans le cadre de ce projet, nous proposons
d'utiliser un seul POM par serveur d'objets persistants (d'autres architectures
seraient envisageables). Un POM doit alors être programmé
comme un singleton, ce qui garantit qu'une seule instance de sa classe
sera créée par machine virtuelle Java.
Nous donnons ci-après l'interface Persistence.PersistentObjectManager
définissant
le comportement d'un POM, dont les méthodes devront être définies
dans la classe Persistence.PersistentObjectManagerlmpl.
package
Persistence;
...
public interface PersistentObjectManager
{
public PersistentObject
load(byte[] pid);
public
void store(java.lang.Object obj, byte[] pid);
public
byte[] register(PersistentObject obj);
...
}
|
3.3. La classe persistentActivator
Le ServantManager que nous allons utiliser au niveau du PPOA ets mis en
oeuvre par la classe PersistentActivator, qui doit étendre la classe
org.omg.PortableServer.ServantActivatorPOA définie par Corba. Deux
méthodes doivent être définies :
-
la méthode incarnate, qui est appelée pour créer un
servant pour un objet identifié par son ObjectId,
-
la méthode etherealize, qui est appelée lorsqu'un objet est
désactivé.
public class PersistentActivator extends org.omg.PortableServer.ServantActivatorPOA
{
...
public org.omg.PortableServer.Servant incarnate(
byte[] id,
org.omg.PortableServer.POA
adapter)
throws org.omg.PortableServer.ForwardRequest
{
...
}
public void etherealize(
byte[] id,
org.omg.PortableServer.POA
adapter,
org.omg.PortableServer.Servant
servant,
boolean cleanup_in_progress,
boolean remaining_activations
) {
...
} |
6
Implication de l'utilisation d'objets persistents sur le modèle
de programmation Corba
6.1 Modèle de programmation
Pour disposer d'objets persistents, il est nécessaire d'utiliser
le modèle de programmation par délégation pour deux
raisons :
-
Les objets Corba que l'on souhaite rendre persistent devront hériter
de la classe PersistentObjectImpl. Le langage Java n'autorise
pas l'héritage multiple, et il n'est donc pas possible de définir
des classes héritant à la fois d'un squelette et de la classe
PersistentObjectImpl.
-
La classe Corba définissant un squelette n'est pas sérialisable,
ce qui rend non sérialisable un objet persistent si celui-ci hérite
de son squelette (la machine virtuelle essayant de sérialiser tous
les objets locaux référencés dans l'objet à
sérialiser).
6.2 Gestion de références persistantes
Lorsqu'un objet O référence un objet O', dans l'état
de l'objet O se trouve une structure de donnée qui sera sauvegardée
avec l'état de O si celui-ci est persistant. Deux problemes peuvent
se poser :
-
la structure de donnée représentant la référence
est-elle sérializable ?
-
la valeur de cette référence est t-elle persistante ? autrement
dit, reste t-elle valide en cas de déchargement et rechargement
de O' ?
Pour traiter cet aspect, nous différencions deux types de références
:
-
Lorsqu'un objet O référence un objet distribué O'
géré par un serveur distinct, nous parlons de références
distantes,
-
Lorsqu'un objet O référence un objet distribué O'
géré par le même serveur, nous parlons de références
locales.
a) Gestion des références distantes
Une référence distante correspond à l'adresse
d'un talon, qui est une structure de donnée locale. Or un talon
Corba n'est pas sérialisable, et ne peut donc pas être persistant.
On prendra donc soin de définir ces références comme
des données transient, c'est à dire dont la valeur ne doit
pas être sauvegardée lors de la serialisation de l'objet qui
les contient.
Pour conserver des références persistentes dans un objet,
on utilisera les méthodes fournies par Corba pour transformer des
références en chaînes de caractères et inversement
:
-
String object_to_string (org.omg.CORBA.Object)
// La chaine retournée correspond à l'IOR stringifiée
-
org.omg.CORBA.Object string_to_object(String)
// l'objet retourné est un talon
Un exemple d'utilisation est donné ci-après.
Soit ref_O',
une référence distante vers un objet O' :
String IOR_O'
= orb.object_to_tring (ref_O')
Pour accéder à
l'objet O', on doit disposer d'une référence sur un talon
de O' obtenue a partir de la référence persistente:
ref_O' =
<class_de_O'>Helper.narrow(orb.string_to_object(IOR_O'))
ref_O'.methode(...) |
b) Gestion des références locales
Dans le cas standard, les références locales sont des
références Java (adresses mémoire). Dans le cas d'objets
persistants, deux problèmes se posent
:
-
une référence Java n'est pas persistante : en cas de décharge
de O' sur disque, l'adresse de O' contenue dans O n'est plus valide.
-
sérializer un objet O contenant l'adresse de l'objet O' engendre
la sérialisation de O et d'une copie de O'.
Pour éviter ces problèmes, nous proposons d'accéder
les objets locaux et persistents comme des objets distants, c'est à
dire que la référence de ces objets doit être une référence
distribuée.
7
Travail à réaliser
Vous avez à implémenter le package Persistence pour fournir
le service de persistence. Ensuite, vous aurez à modifier l'implémentation
actuelle de l'application bancaire de manière à ce que les
objets gérés par cette application deviennent persistents.
En particulier, vous aurez à garantir les aspects suivants.
-
Si cela n'est pas déjà le cas, utiliser le modèle
de programmation par délégation.
-
Adapter le code de lancement des serveurs Corba, pour que ceux-ci utilisent
un PPOA (Persistent POA).
-
Faire en sorte que les implémentations des objets persistents étendent
la classe PersistentObjectImpl et surchargent si nécessaire la méthode
init.
-
Transformer les références distribuées et les références
locales vers des objets persistents en des références distribuées
persistentes.