XML-RMI et passage d`objets en mode valeur/résultat

Transcription

XML-RMI et passage d`objets en mode valeur/résultat
UFR 919 Sciences de l’ingénieur
Master Informatique – Spécialité STL
ALADYN
XML-RMI et passage d’objets
en mode valeur/résultat
version 1.2 du 24/10/2012
Jacques Malenfant
professeur des universités
Résumé
L’objectif de ce projet est de comprendre les possibilités offertes par l’outil Javassist par
ses capacités de génération dynamique de code, et ce par la réalisation d’un système d’appel
de méthodes à distance en XML pour des objets répartis en Java.
1
Appel à distance
L’appel d’une méthode à distance entre deux ordinateurs, clé de la programmation répartie,
demeure un maillon relativement simpliste même dans les approches les plus sophistiquées utilisées
aujourd’hui dans l’industrie. En effet, la plupart des protocoles qui ont été implantés à ce jour
demeurent limités (comme SOAP, par exemple) ou très lourds (comme Java RMI) dès qu’il s’agit
de passer des objets en paramètres et de les recevoir en résultat.
Dans ce projet, nous allons nous poser comme défi d’être capables de passer des objets en
paramètres à des appels de méthodes à distance, de recevoir des objets en résultat, et de récupérer
des modifications à l’état des objets passés en paramètres pendant l’exécution de la méthode
distante. En Java RMI, pour ce faire, il faut que les objets passés en paramètres soient euxmêmes des objets distants, modifiés par des appels de méthodes distants en retour, ce qui est très
pénalisant.
Nous allons donc tenter une autre approche : un passage par valeur/résultat, c’est-à-dire que
l’objet passé en paramètre sera recréé sur le site distant, modifié localement par la méthode
appelée, puis retourné à l’appelant pour récupérer ses modifications en fin d’exécution de l’appel.
Dans le domaine des langages de programmation, ce genre d’interaction sur les paramètres où la
valeur du paramètre n’est remise à jour dans le contexte de l’appel qu’à la fin de l’exécution de la
méthode appelée est généralement désignée sous le nom « d’appel par valeur/résultat ».
2
XML-RMI
Pour se mettre dans un contexte de programmation répartie, nous allons nous baser sur une
approche relativement simple : XML-RPC. XML-RPC est un protocle minimaliste d’appel de
procédures à distance où la sérialisation des paramètres et du résultat utilise un format XML
et où la communication entre hôtes distants se fait par la protocole HTTP. Sa popularité est
due à cette simplicité permettant d’implanter des services Web à peu de frais. La spécification
XML-RPC prévoit uniquement les types suivants comme paramètres ou résultat d’un appel :
–
–
–
–
entiers (int),
réels (double),
booléens (boolean),
chaînes de caractères (string),
–
–
–
–
moments dans le temps (dateTime ISO8601),
binaire (base64),
des tableaux (array) de valeurs homogènes des autres types de la norme, et
des valeurs structurées struct contenant des champs nommés avec des valeurs hétérogènes
des autres types de la norme.
Plus précisément, voici un exemple d’appel en XML-RPC :
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param><value><int>41</int></value></param>
</params>
</methodCall>
La spécification complète de XML-RPC se trouve à l’URL http://xmlrpc.scripting.com/
spec.html. Plutôt que le protocole HTTP pour échanger entre hôtes, nous verrons plus loin que
notre utilisation se fera plutôt dans le contexte d’une communication par « sockets ».
Le projet consiste à implanter ce protocole en lui ajoutant un nouveau type de données
« object » pour obtenir ce qu’on va appeler du XML-RMI. La figure 1 présente un schéma
Relax-NG permettant de valider le contenu XML d’une requête ou d’un résultat en XML-RMI.
On constate que la spécification distingue les appels de méthodes des résultats qui seront retournés. Un appel contient un nom de méthode puis les valeurs des paramètres. Les types conservés
de la spécification XML-RPC sont, comme indiqué plus haut : int (ou i4), double, boolean
(mais avec les valeurs numérioques explicites 0 et 1, et non true et false), string, dateTime,
base64 (binaire encodé selon la norme), array et struct. En XML-RMI, nous ajoutons le type
object pour passer des objets en paramètre ou les retourner en résultat, ce qui sera discuté dans
la prochaine section.
3
Des objets en XML pour XML-RMI
Le cœur du projet va consister à effectivement réaliser le passage d’objets en paramètres pour
ce protocole XML-RMI que nous inventons pour l’occasion. Le principe est de sérialiser les objets
sous la forme d’une liste de champs (« fields ») et d’une liste de méthodes. Les champs seront
définis par un nom et une valeur initiale, alors que les méthodes seront données sous la forme d’un
texte représentant le code de la méthode dans un certain langage de programmation, comme Java.
Quelle est l’idée ? D’une part, il s’agit d’être capables de sérialiser des objets pour les passer en
paramètre, ce qui se réalise à l’aide du schéma RelaxNG de la figure 2. Certes, une telle sérialisation
n’est pas nouvelle, puisque la norme RMI de Java le fait déjà. Mais ici, on ne sérialise pas l’objet,
on crée plutôt un représentant de l’objet qui va être transféré côté appelé pour y être utilisé tout
au long de l’exécution de la méthode appelée. Cette idée a plusieurs avantages :
– ne pas avoir besoin de revenir côté appelant à chaque fois que la méthode appelée veut utiliser
l’objet passé en paramètre,
– permettre à l’appelant de ne passer qu’une partie de l’objet à l’appelé (ne pas y mettre tous
les champs ni toutes les méthodes), ce qui est utile pour des objets dont une partie n’a pas
à être vue par l’appelé (par exemple, pour des raisons de sécurité des données),
– rendre le code appelé plus indépendant que dans SOAP de la structure du XML passé en
paramètre en permettant d’appeler les méthodes de l’objet plutôt que d’accéder directement
aux champs, et
– améliorer l’interopérabilité, en ayant la possibilité de passer des objets lors d’appels entre
objets répartis écrits dans différents langages de programmation (par exemple, un objet en
Python peut appeler un objet écrit en Java, en prenant soin de définir en Java les méthodes
des objets passés en paramètres en XML).
#
#
#
#
#
*****************************************************************************
ALADYN -- Architectures logicielles pour l’auto-adaptabilité dynamique.
Copyright (C) 2012 <[email protected]>
GPL version>=2
*****************************************************************************
start = methodcall | methodresponse
#******************************************************************************
# Appel par le client
#******************************************************************************
methodcall
= element methodCall {
methodname, element params { param+ }?
}
methodname = element methodName { xsd:string {pattern = "[a-zA-Z0-9_\.,/]+"} }
param
= element param
{ value }
value
= element value
{ typedvalue | text }
typedvalue = integer | boolean | stringval | # string is a keyword
double | datetime | base64 | array | struct | object
integer
= element i4
{ xsd:int } |
element int
{ xsd:int }
boolean
= element boolean
{ "0" | "1" }
stringval
= element string
{ text }
double
= element double
{ xsd:double }
datetime
= element dateTime.iso8601 {
xsd:string {pattern =
# xsd:date does not allow "20051130T12:10:56"
"[0-9]{4}[0-1][0-9][0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]"}
}
base64
= element base64
{ xsd:base64Binary }
array
= element array
{ element data { value+ } }
struct
= element struct
{ namedmember+ }
namedmember = element member
{ membername & value }
membername = element name
{ xsd:NMTOKEN }
object
= external "object.rnc"
#******************************************************************************
# Retour du resultat du client
#******************************************************************************
methodresponse = element methodResponse {
fault | element params { param+ }
}
fault
= element fault
{ faultvalue }
faultvalue
= element value
{ faultstruct }
faultstruct
= element struct {
(faultcode, faultstring) | (faultstring, faultcode)
}
faultcode
= element member {
element name { "faultCode" } & element value { integer }
}
faultstring
= element member {
element name { "faultString" } & element value { stringval | text }
}
Figure 1 – Le schéma RelaxNG pour XML-RMI.
#
#
#
#
#
*****************************************************************************
ALADYN -- Architectures logicielles pour l’auto-adaptabilité dynamique.
Copyright (C) 2005 <[email protected]>
GPL version>=2
*****************************************************************************
start = object
object = element object {
attribute oid { xsd:anyURI },
attribute type { xsd:NMTOKEN }?,
element fields { field* },
element methods { method* }
}
field = element field {
attribute name { xsd:NMTOKEN },
parent value
# value pattern as defined in the parent schema
}
method = element method {
attribute language { xsd:NMTOKEN }?, # programming language used
text
# method definition
}
Figure 2 – Le schéma RelaxNG pour la représentation des objets en XML-RMI.
Bien sûr, une fois l’appel reçu côté appelé, l’objet en XML ne peut être utilisé tel quel. C’est
là où la réflexion et les approches vues en ALADYN entrent en jeu pour permettre de créer à la
volée un objet Java à partir de l’objet XML. Nous allons poser comme hypothèse que toutes les
méthodes qui peuvent être appelées par XML-RMI sont définies avec une signature où tous les
types des paramètres sont soit des types primitifs du protocole XML-RPC, soit des objets dont le
type est donné par une interface Java. Cette interface peut être déterminée soit par le contexte,
lorsque l’objet correspond à un paramètre de la méthode appelée sur le serveur, ou être nommée
explicitement dans l’attribut type (optionnel) de la représentation XML-RMI lorsque l’objet est
utilisé dans un autre contexte (dans un tableau, comme valeur d’un champ dans un objet, ...).
L’objet XML devra donner une définition pour chacune des méthodes apparaissant dans l’interface
Java en question. En utilisant Javassist à la réception de l’appel en XML-RMI, vous allez créer
une classe pour représenter cet objet puis instancier cette classe et initialiser l’objet obtenu avant
de l’utiliser dans l’appel à la méthode de l’objet appelé.
Par exemple, soit un objet réparti offrant une méthode display de la forme suivante :
public void display(Stringable s) {
System.out.println("Un point 2D : " + s.toString()) ;
}
L’interface Stringable typant le paramètre s est définie de la manière suivante :
public interface Stringable {
public String toString() ;
}
Un exemple de contenu XML-RMI pour l’appel de la méthode display avec un paramètre
objet correspondant à l’interface Stringable prend la forme suivante :
<methodCall>
<methodName>display</methodName>
<params>
<param>
<value>
<object oid="http://www.jm.fr/1000000"
type="Stringable">
<fields>
<field name="x"><value><double>10.0</double></value></field>
<field name="y"><value><double>20.0</double></value></field>
<field name="mark"><value><string>@</string></value></field>
</fields>
<methods>
<method language="Java">
public String toString()
{
return this.x + this.mark + this.y ;
}
</method>
</methods>
</object>
</value>
</param>
</params>
</methodCall>
On voit que cet objet est un point 2D avec trois champs représentant l’abcisse, l’ordonnée
et un caractère utilisé comme séparateur pour produire une représentation sous forme de chaîne
de caractères avec le format suivant : [email protected]. Mais la production de ce format à partir des
données est masquée par la méthode toString de l’interface Stringable.
Notez que le schéma RelaxNG de XML-RMI diffère de celui de XML-RPC en deux points.
D’abord, on introduit le type object comme déjà mentionné. Mais il y a une autre modification
au niveau de la réponse. Là où XML-RPC ne prévoit qu’une valeur de retour, nous permettons
plusieurs « paramètres » en retour. Par convention, le premier paramètre en retour sera toujours
le résultat de la méthode appelée, mais l’appelé poura inclure tout ou partie des objets passés en
paramètres lors de l’appel pour retourner à l’appelant les nouvelles valeurs des champs de ces objets
après exécution de la méthode. Ainsi, l’appelant pourra mettre à jour les valeurs des champs dans
les objets originaux de son côté. Mais pour cela, il faut qu’il puisse identifier les objets retournés.
C’est pour cela que la représentation du type object prévoit un identifiant d’objets unique de
la forme d’une URI. L’appelant peut donc mémoriser lors de l’appel la correspondance entre les
identifiants uniques et les références locales à ses objets, et au retour utiliser cette correspondance
pour mettre à jour l’objet original.
Ainsi, dans l’exemple précédent, la réponse aurait la forme suivante :
<methodResponse>
<params>
<param>
<value>void</value>
</param>
<param>
<value>
<object oid="http://www.jm.fr/1000000">
<fields>
<field name="x"><value><double>10.0</double></value></field>
<field name="y"><value><double>20.0</double></value></field>
<field name="mark"><value><string>@</string></value></field>
</fields>
<methods></methods>
</object>
</value>
</param>
</params>
</methodResponse>
Dans cet exemple, on constate que le retour est void, car la méthode display n’a pas de valeur
de retour (mais cela aurait pu être une valeur au sens value de XML-RMI dans un autre cas).
L’objet apparaissant dans le retour est identique à celui passé en paramètre lors de l’appel car
on présume que display ne modifie pas la valeur de son paramètre. Néanmoins, l’appelé peut le
retourner et l’appelant le traiter sans chercher à savoir s’il a été modifié ou non. Au retour, seuls
les champs sont utiles, et on arrive à déterminer qu’il s’agit d’un objet précédemment passé en
paramètre par l’identifiant unique oid qui est le même qu’un de ses objets locaux.
Une manière systématique d’implanter un objet en Java pouvant être passé en paramètre via
XML-RMI serait de lui faire implanter l’interface suivante :
public interface XMLRMISerializable {
public String toXML(Class<?> inter) ;
public void updateFromXML(org.w3c.dom.Element theXML) ;
}
La méthode toXML sert à produire la sérialisation en XML souhaitée en fonction de l’interface
attendue par le service (on pourra ainsi produire un XML différent en fonction de l’interface
demandée), alors que la méthode updateFromXML met à jour l’objet à l’aide de la réprésentation en
DOM de l’objet en XML extraite de la réponse de l’appel distant. De cette manière, le déroulement
standard d’un appel distant serait :
1. Encoder l’appel en XML-RMI en utilisant les méthodes toXML des objets à passer en paramètres pour obtenir leur représentation en XML.
2. Envoyer l’appel sur le « socket » de l’objet appelé.
3. Récupérer la réponse en XML pour en décoder le résultat et mettre à jour pour chacun des
objets en retour les objets originaux en appelant la méthode updateFromXML de ces derniers.
Cette mécanique de mise à jour en retour ne fonctionenra que dans le cadre d’un appel. Le
résultat de la méthode, bien que pouvant être un objet, ne sera pas considéré comme partagé
entre l’appelé et l’appelant. Ceci pour limiter la difficulté du projet, sinon il faudra gérer ces
objets partagés sur l’ensemble d’une conversation entre deux ou plusieurs objets. Mais si vous
souhaitez vous lancer dans cette direction, pourquoi pas ?
Par ailleurs, pour ne pas avoir à réimplanter la sérialisation des champs et la récupération
des valeurs en retour pour chaque objet pouvant être passé en paramètre ou reçu en résultat,
l’annotation suivante permettra de retouver les champs à sérialiser et à mettre à jour au retour :
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface XMLRMIField {
String serializationName() ; // nom à utiliser dans la sérialisation XML-RMI
String serializationType() ; // type XML-RMI à utiliser dans la sérialisation
}
Par exemple, la classe Point suivante pourrait avoir servi à produire l’objet passé en paramètre
de l’exemple d’appel précédent :
public class Point {
@XMLRMIField{serializationName="x",serializationType="double"}
protected double a ;
@XMLRMIField{serializationName="y",serializationType="double"}
protected double b ;
@XMLRMIField{serializationName="mark",serializationType="string"}
protected char marque ;
}
Grâce à ces annotations, il vous sera possible d’implanter une fois pour toute par réflexion la
sérialisation des champs et la récupération des valeurs en retour. Notez bien que cette annotation
permet d’utiliser un nom de champ et un type de valeur en XML qui est différent de celui utilisé
en Java ; vous devrez faire les conversions nécessaires dans les deux sens.
4
Intégration dans un modèle d’objets répartis
Pour mettre en œuvre le concept d’objets en XML dans un contexte réaliste, vous devrez le
tester avec un modèle d’objet réparti volontairement simple pour rester réalisable dans le cadre
de ce projet.
En Java, le modèle d’objets répartis le plus simple est celui proposé autour de l’appel de
méthodes à distance RMI. Un objet réparti serveur RMI implante (au moins) une interface Java qui
hérite elle-même directement ou indirectement de l’interface java.rmi.Remote, ce qui lui permet
de publier sa propre référence sur l’annuaire RMI (RMIRegistry) et à ses cleints de la récupérer par
une requête à cet annuaire. L’appel à distance lui-même se fait en utilisant la référence RMI ainsi
obtenue, et le protocole d’échange entre machines virtuelles (et éventuellement hôtes) demeurant
caché dans l’implantation de RMI.
Pour notre XML-RMI, l’idée sera la suivante : la référence à un objet réparti se fera grâce à
l’identification Inet ou l’adresse IP de l’hôte exécutant la MV Java où se trouvbe l’appelé et du
numéro de port sur lequel cet objet va écouter les requêtes qui lui seront faites sous forme d’appel
de méthode à distance utilisant le protocole XML-RPI décrit précédemment. Un objet réparti
sera donc un objet possédant son propre fil d’exécution (« thread ») et qui va répétitivement
écouter sur un « socket » les requêtes en XML-RMI, les exécuter puis retourner le résultat dans
le format XML-RMI à son appelant. Des informations sur la programmation de communication
par « socket » en Java sont largement disponibles sur le WWW.
Pour simplifier la réalisation du projet, vous pourrez considérer que l’adresse IP de l’hôte sur
lequel se trouve l’objet et le numéro de port sur lequel ce dernier écoute sont des informations
connues à l’avance et considérée comme constante par le client. Les étudiants les plus performants
pourront étendre leur projet en ajoutant un annuaire pour le protocole XML-RMI permettant
de centraliser les références et les requêtes pour la connexion en s’inspirant de l’annuaire RMI
Registry de Java.
5
Réalisations attendues
Nous vous demandons de réaliser le projet sous Eclipse en prévoyant tout le code nécessaire
pour lancer l’application et dans votre rendu vous devrez également joindre des tests unitaires
JUnit dans des sous-packages tests.
Modalités de remise des projets :
– Le projet se fait obligatoirement en binôme. Tous les fichiers du projet doivent
comporter les noms (authors) de tous les auteurs.
– Le projet est à rendre le vendredi 16 novembre 2012 à minuit au plus tard sous la
forme d’une archive tgz si vous travaillez sous Unix ou zip si vous travaillez sous Windows
que vous m’enverrez à [email protected] comme attachement fait proprement
avec votre programme de gestion de courrier préféré. Donner pour nom à votre archive vos
numéros de dossier d’étudiants sous la forme n-n (ex. : 2700001-2800002.tgz). Attention,
peu importe le système d’exploitation sur lequel vous travaillez, il faudra que votre projet
s’exécute correctement sous Eclipse en Mac Os X (que j’utilise)
– Votre projet utilisera JUnit pour faire des tests de vos classes. Il comportera aussi des classes
concrètes (application) permettant de réaliser les tests. Tous vos tests seront regroupés dans
un répertoire spécifique tests au même niveau que le répertoire src dans votre projet
Eclipse. Utilisez exactement la même structure de paquetage dans le répertoire tests que
dans le répertoire src.
– Le code doit être documenté en Javadoc (générée et incluse dans votre livraison dans un
répertoire doc au même niveau que votre répertoire src) et commenté. Le code doit également
être correctement formatté (indentation, ...).
– L’évaluation comportera une soutenance d’une quinzaine de minutes où vous devrez démontrer votre projet (prévoyez dans le code envoyé tout ce qu’il faut pour faire une telle
démonstration) et répondre à des questions sur ce dernier. Les soutenances auront lieu
dans la semaine du 19 novembre 2012 selon un horaire de passage qui sera publié le
moment venu sur le site de l’UE. Tout retard à ces soutenances entraînera son annulation et
une pénalité sera appliquée sur l’évaluation du projet.
– Tout manquement à ces règles élémentaires entraînera une pénalité
dans la note du projet !

Documents pareils