Accueil > Java EE > Le Rhino féroce !

Le Rhino féroce !

Le jeu de mots n’est pas de moi, mais de notre ami Henri Darmet qui nous propose cet article.

Bon, première question : qu’est-ce que Rhino ? J’en entends quelques uns qui ricanent dans le fond : ils savent, eux. Pour les autres, ceux qui ont l’air étonné, sachez que cet article est d’abord écrit pour vous. Tant pis pour les autres !

La bête à corne est en fait une bête fonctionnalité présente dans le JDK depuis des années, depuis le JDK 1.5, en fait, autant dire la nuit des temps informatiques… Pas si bête, en fait, la fonctionnalité, puisqu’il s’agit d’un interpréteur JavaScript disponible à tout bon développeur Java en… huit lignes de code. Vous ne me croyez pas ? Et bien, ces huit lignes, les voici :

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
engine.put("myVar", maVariableEnEntree);
try {
 engine.eval(scriptJS);
 myReturnValue = engine.get("myResult ");
} catch (ScriptException e) {
 …
}

« Plus le maven, bien sûr, le téléchargement, les problèmes qui vont avec… », me direz-vous. Eh bien non, même pas. Inside the JDK. Yes ! Si, si. Dispo tout de suite, il n’y a qu’à se baisser.

Deux-trois explications sur le code donné ci-dessus (pour les plus curieux) : le script à exécuter est contenu dans la variable scriptJS (une string). Ce script utilise (entre autres) une variable JavaScript nommée myVar que je positionne avant de le lancer le script par un appel à « engine.put ». Lors de l’exécution du script, la variable javascript « myVar » est disponible avec comme valeur initiale, celle afectée dans le code java. Une fois le script exécuté, je peux récupérer des résultats en extrayant la valeur d’une (ou plusieurs) variables, comme ici « myResult ». L’exemple présenté ci-dessus est très simple, il n’y a qu’une variable en entrée et une autre en sortie. Rien ne vous empêche d’en avoir 25154322 en entrée et 357652 en sortie. Il suffit pour cela d’appeler 25154322 « engine.put » d’abord et 357652 fois « engine.get », ensuite. Qu’est ce qu’il dit l’autre là-bas, au fond, qui ricane ? Que nous risquons d’avoir un problème de perf ? Ou de consommation mémoire ? Objection retenue. Rhino, sur le JDK reste un interpréteur.

Mais à quoi peut bien servir un interpréteur JavaScript au sein du JDK ?

A priori, pas à grand-chose. Peut-être à faire un peu mumuse lorsqu’il est 17h45 et qu’on n’a plus le temps de se mettre à une nouvelle tâche avant le métro et dodo qui suivent le boulot. Bof…. C’est vrai, quoi ! Nous ne sommes pas au sein d’un navigateur ! Cela ne nous permet pas de faire apparaître (à prix d’or) de beaux composants Web qui émerveillent nos clients (et vident leurs portes-monnaies).

Erreur : C’est très utile un interpréteur de script au sein d’un langage compilé, surtout, surtout, comme c’est le cas ici, lorsqu’il est très bien intégré au langage qui l’héberge. A quoi cela peut-il être utile ? A donner du dynamisme là où il n’y en a pas.

Un exemple peut-être pour rendre les choses plus concrètes ?

Nous fabriquons un petit calculateur et notre utilisateur doit pouvoir entrer une formule de calcul. Un champ de saisie, un boutons et un label résultat et hop, notre bout de page web est prêt. L’utilisateur va pouvoir entrer sa formule et appuyer sur le bouton pour que dans le label résultat, le résultat du calcul s’affiche.

Le gros du travail, vous l’avez compris consiste à interpréter la formule de calcul. Il existe des librairies plus ou moins opérationnelles, plus ou moins simple à utiliser. Mais toutes, évidemment, nécessitent un apprentissage et l’écriture de dizaines de lignes de code, pour le moins. Avec Rhino, c’est très simple. Il suffit d’entourer la formule saisie par le texte javascript nécessaire à son fonctionnement :

ScriptEngineManager manager = new ScriptEngineManager();
 ScriptEngine engine = manager.getEngineByName("js");
 try {
 engine.eval("var res="+getFormulae()+";\n");
 double resultat =
 (Number)engine.get("res")).doubleValue();
 return new Float(resultat);
 } catch (ScriptException e) {
 …
 }

Et le tour est joué ! Bien sûr, l’utilisateur peut se tromper dans sa formule et l’interpréteur sera alors incapable de l’exécuter. Un message d’erreur devra être pertinent. Comment ? En récupérant le message renvoyé par javascript – en français, s’il vous plait – et en en retirant les scories pour en faire quelque chose de très présentable. Par exemple, la méthode suivante fait le boulot :

public static String processMessage(ScriptException e, int delta) {
 String msg = e.getLocalizedMessage();
 msg = msg.substring(msg.indexOf(":")+1);
 msg = msg.substring(msg.indexOf(":")+1,
 msg.indexOf("( for (Map.Entry entry : env.entrySet()) {
 engine.put(entry.getKey(), entry.getValue());
 }
 try {
 engine.eval(script);
 } catch (ScriptException e) {
 processMessage(e, 0);
 }
 }
}

Autre cas de figure ou Rhino peut sauver votre projet d’une affreuse complexité : les jeux de règles. Il n’est pas rare (et même très fréquent) qu’on ait besoin de règles métier que l’on doit pouvoir enrichir ou amender dynamiquement, c’est-à-dire définissable sans que l’application soit recompilée, voire sans même qu’elle soit redémarrée !

Beaucoup pensent : jeux de règles donc moteur de règles. C’est une erreur : un moteur de règles sert à exécuter un ensemble de règles sur un très gros volume de données de façon optimisée en cherchant à en maximiser l’exécution. C’est rarement notre besoin. Celui-ci est souvent fort simple : sur un contexte réduit (souvent un seul objet), j’ai un ensemble de règles à exécuter, je dois toutes les exécuter, ou les exécuter dans un ordre précis jusqu’à ce qu’il y en ait une qui échoue. Nous n’avons pour cela besoin d’aucune complexité particulière. La seule difficulté est que les règles doivent être définies dynamiquement. Rhino est parfait pour cela. Voici un exemple de « moteur de règles Rhino » :

On crée une classe Rule dont l’étude ne devrait pas provoquer de migraines :

public class Rule {
String script;

public String getScript() {
return script;
}
public void setScript(String script) {
this.script = script;
}

public void execute(Map env) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
for (Map.Entry entry : env.entrySet()) {
engine.put(entry.getKey(), entry.getValue());
}
try {
engine.eval(script);
} catch (ScriptException e) {
processMessage(e, 0);
		}
}

}

 Le moteur lui-même est une simple boucle :

public void execute() {
 for (Rule rule : getQuestionnaire().getRegles()) {
 rule.executer(env);
 }
 }

A affiner dans votre contexte évidemment, en particulier s’il faut récupérer des valeurs résultant de l’exécution de ses règles. Dernière tâche, bien sûr : enrichir votre interface d’administration pour pouvoir créer de nouvelles règles, amender celles qui existent, en retirer, éventuellement. Bravo, vous venez d’économiser 50 Mo de jars, 100 heures de travail. Ok, ceux du fond, vous avez raison. C’est vrai qu’on vient de perdre une merveilleuse occasion d’étudier, aux frais de la princesse, le merveilleux outil qu’est Drools (voir précédente discussion sur les BRMS (Business Rule Management System)).

Est-ce tout ? Non, j’ai gardé le meilleur pour la fin : on peut utiliser la bête à corne pour construire une solution de paramétrage de votre application aussi simple que puissante et élégante !

Comment ?

En utilisant une particularité de Rhino : toutes les méthodes des classes de la JVM sont accessibles en javascript. On peut tout faire : créer un objet, appeler une méthode, imbriquer des appels en utilisant les instructions conditionnelles, etc. Moralité : il suffit de passer l’objet à paramétrer à un script Rhino – par exemple, une Widget – pour qu’ensuite cet objet se voit spécialisé en toute dynamicité, sans même avoir à définir d’interface de spécialisation. Non seulement on peut changer la couleur de fond et la police de caractères, mais on peut carrément altérer sa structure : rien ne nous empêche d’en « finir » la construction en rajoutant une colonne à un tableau, un champ de saisie à un formulaire ou un nouvel onglet !

Ce troisième type d’usage est à mon sens sans prix pour les sociétés éditrices de logiciel car il cumule tous les avantages dont elles ont besoin :

  • Ne pas complexifier les objets du framework pour les rendre paramétrables,
  • Expressivité très puissante, afin de faire face à des besoins impossibles à cadrer à priori.

L’inconvénient – il en faut un – est qu’en soumettant les objets de votre application à un script modifiable dynamiquement, vous ouvrez la boite de Pandore. Tout devient possible d’un claquement de doigt, le pire aussi devient possible… Cette fonctionnalité est donc à réserver aux utilisateurs avertis qui sont, dans le cas d’une société éditrice de logiciels, les membres de l’équipe chargée de l’installer en clientèle. Il ne doit pas être possible de l’utiliser à partir des écrans « standards » de l’application : seule l’administration devrait y être autorisée.

Categories: Java EE Tags: ,
  1. Pas encore de commentaire
  1. Pas encore de trackbacks


un × 3 =