Le Graal du projet cajo
De quel graal est-il question ? Grail, ou graal en français, est le nom d'une interface Java définie dans le projet cajo ; cajo est ce framework compact, simple et puissant, permettant de réaliser des applications Java distribuées.
Dans l'article Simple Inter-JVM communication... The Grail! portant sur cajo, John Catherino y décrit l'interface Java Grail, et en particulier trois de ses méthodes, dont le but est de faciliter l'interopérabilité entre machines virtuelles Java.
Voici, en résumé, le rôle de chacune de ces méthodes :
- export, qui exporte un objet en rendant ses méthodes public appelables de manière distante ;
- lookup, qui renvoit une liste d'objets distants supportant une interface particulière ;
- et proxy, qui permet de manipuler une référence distante comme si c'était une référence d'un objet local.
A titre d'illustration, nous allons montrer comment mettre en oeuvre cette approche dans une application web de partage de documents, que nous appellerons Gfiles, développée à l'aide de framework web agile Grails.
La version du projet cajo utilisée est la version 1.115 (la dernière au moment où ces lignes sont rédigées), pour laquelle j'ai pu apporter une petite contribution (voir cajo release notes)!
Gfiles, une application distribuée de partage de fichiers
Gfiles est donc une application web Grails possédant deux fonctions principales.
Premièrement, elle permet le partage de fichiers placés dans un répertoire particulier de la machine qui héberge l'application web ; par ailleurs, l'application Gfiles est en mesure de présenter dans la même page web, les fichiers partagés par d'autres instances de l'application s'exécutant sur d'autres machines du réseau.
Deuxièmement, un utilisateur de l'application Gfiles a la possibilité de demander la récupération automatique, dans le répertoire de partage de l'application, d'un fichier partagé par une autre instance de Gfiles.
L'application Gfiles se comporte donc soit comme client, ou soit comme serveur, vis-à-vis d'une autre instance de l'application.
Partie serveur de Gfiles : exporter des objets distants depuis un service Grails
Une instance de l'application Gfiles doit être en mesure de fournir une liste des fichiers présents dans son répertoire de partage ; c'est pourquoi, l'application exporte un POGO (Plain Old Groovy Object) possédant la méthode public :
Cette méthode est en fait définie dans l'interface com.odelia.grailsbox.cajo.Interface implémentée par notre POGO de type GrailService.
Une instance de la classe Groovy GrailService est exportée à l'aide d'une instance de la classe gnu.cajo.Cajo qui implémente l'interface gnu.cajo.Grail, au sein d'un service Grails :
import gnu.cajo.invoke.Remote
import gnu.cajo.utils.ItemServer
import gnu.cajo.utils.extra.Xfile
import com.odelia.grailsbox.cajo.Interface
class CajoService {
boolean transactional = false
def cajo = null
def xfile
def start(dir) {
if (!cajo) {
def service = new GrailService(dir)
cajo = new Cajo(1198, null, null)
ItemServer.bind(service, "service")
xfile = new Xfile(1024)
Xfile.remoteInvoke = true
ItemServer.bind(xfile, "Xfile")
cajo.export(service)
log.info "GrailService exporté !"
}
}
def lookup() {
def refs = cajo.lookup(Interface.class)
def proxies = []
refs.each {
proxies << (Interface) cajo.proxy(it, Interface.class)
}
proxies
}
}
Ainsi, lors du démarrage de l'application Gfiles, il est nécessaire d'appeler la méthode start() du service Grails CajoService, en indiquant le répertoire à partager.
L'objet de type GrailService est également enregistré dans un registre RMI local, ce qui permettra à un client de récupérer une référence distante afin d'invoquer la méthode getDocumentList(). Nous reviendrons plus loin sur cette possibilité.
Un autre objet, de type gnu.cajo.utils.extra.Xfile, est également créé dans le code de la méthode start() ; Xfile est une classe utilitaire du projet cajo permettant la copie de fichiers d'une JVM vers une autre, par paquets d'octets. Ici, l'instance Xfile est enregistrée dans le registre RMI local : un client, comme par exemple une autre instance de l'application Gfiles, va pouvoir demander la recopie d'un fichier placé dans le répertoire de partage, vers un autre emplacement distant.
A la recherche des documents... distants
Le service Grails GrailService possède également le méthode lookup() qui, grâce à l'objet Cajo, permet de renvoyer toutes les objets distants qui implémentent l'interface Interface. Il est ainsi possible de récupérer les références distantes vers les objets GrailService qui sont exposés par toutes les instances de Gfiles qui s'exécutent sur le réseau. Et sur chacune de ses références, on peut donc invoquer la méthode getDocumentList().
Cette méthode renvoie une chaîne de caractères qui est un document XML ; en voici un exemple :
<document host="alice">logo.gif</document>
<document host="bob">lisezmoi.txt</document>
</documents>
Partie cliente de Gfiles : dresser la liste des documents locaux et distants
Voyons comment un contrôleur Grails, disposant d'une référence vers le service CajoService, peut permettre l'affichage, dans une page web, de la liste des documents partagés qu'ils soient locaux ou distants.
Une action de ce contrôleur peut faire appel à la méthode lookup() du service, et construire une liste dont chaque élément est une Map Groovy reprenant les informations suivantes pour un document : nom de la machine hôte (host), URL du document (href) et nom du document (name); cette liste est ensuite transmise à la vue comme partie du modèle pour la page .gsp.
flash.message = proxies ? "${proxies.size()} service(s) trouvé(s) !" :
'Aucun service trouvé !'
def documents = []
proxies.each { service ->
documents += new XmlSlurper().parseText(service.getDocumentList()).document.collect {
[host: it.@host, href: "http://${it.@host}:${request.localPort}/grailsbox/${partage}/${it}", name: it]
}
}
render(view: 'client', model: [serverHost: "$Remote.serverHost", documents: documents])
Selon le code présenté ci-dessus, la vue client.gsp est chargée de générer la page HTML dressant la liste des documents provenant de toutes les instances de Gfiles ayant été découvertes sur le réseau :

Récupérer des documents distants
Tout document présenté dans la page peut bien sûr être lu ou récupéré au moyen du navigateur Internet ; la vue Grails client.gsp propose un autre moyen de recopie d'un document : utiliser le lien « Récupérer » qui s'affiche à droite du lien donnant accès au document, dès lors que le document provient d'une machine hôte distante. Un click sur ce lien déclenche l'exécution d'une action Grails qui va copier le document ciblé vers le répertoire de partage de la machine hôte de l'application Gfiles.
Le code de cette action ressemble à ceci :
Xfile.fetch(xfile, "http://${params.host}:${request.localPort}/grailsbox/partage/${params.name}", "${partage}/${params.name}")
redirect(action: client)
La recopie du document repose sur l'utilisation de la class Xfile : dans cette action, nous obtenons dans la variable xfile une référence vers l'objet distant Xfile enregistré par toute instance de Gfiles ; puis, par appel à la méthode static fetch le document distant est copié dans le répertoire de partage local.
Vous avez certainement noté l'utilisation de la variable implicite params par laquelle nous pouvons obtenir les valeurs des paramètres host et name associés au lien « Récupérer » ; en effet, la vue client.gsp génère ce lien en y précisantt les paramètres indiquant le nom de la machine hôte et le nom du document associé.
Autres clients
Le code de l'action présenté ci-dessus montrait une utilisation de la référence lié au nom logique Xfile ; une telle référence permet à tout client Java de réaliser des appels distants sur un objet auquel on peut se lier à l'exécution.
Donnons un autre exemple, cette fois en se liant à l'objet de nom logique service (notre POGO GrailService), au moyen d'une URL de la forme :
//<machine_hote>:1198/service
On peut ainsi demander la liste des documents partagés par une instance de l'application Gfiles s'exécutant sur une machine distante, de la manière suivante :
def serverHost = '…' // machine hôte : nom ou adresse IP
def service = Remote.getItem("//${serverHost}:1198/service")
def xml = service.invoke('getDocumentList', null)
Ce code peut même s'exécuter à partir de la console Groovy (à condition d'avoir ajouté la librairie cajo.jar dans le répertoire lib de la distribution Groovy par exemple) !
Nous aurions également pu combiner cajo avec une application Java de bureau classique ; sans doute le ferons nous avec le nouveau framework très prometteur Griffon !