RHINO PAR LA PRATIQUE

Cet article représente un simple tutoriel permettant de comprendre les concepts de base de programmation avec Rhino. Le but est de se familiariser avec un environnement de programmation JavaScript côté serveur. Des notions sont définies et expliquées au fur et à mesure de l'avancement du tutoriel.

Exemple N° 1: Vérification des données saisies par l'utilisateur

L'une des utilités de JavaScript côté client est la vérification des données entrées par l'utilisateur dans un formulaire avant de les envoyer au serveur. Généralement un deuxième test de ces données est effectué côté serveur. Le test est bien évidemment codé en langage serveur. Dans cet exemple nous allons voir comment utiliser le même code JavaScript côté client et côté serveur, ainsi qu'une manière d'intégrer Rhino dans une application web.

Supposons qu'on a un formulaire avec deux input code postal et émail.

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title>Test RHINO</title>

</head>

<body>

Veuillez saisir les données suivantes:

<form action="J3Sservlet" method="post">

Code Postal : <input type="text" id="ZipCode" name="ZipCode" onchange="validateZipCode(document.getElementById('ZipCode').value)"></input><br>

Email : <input type="text" id="Email" name="Email" onchange="validateEmail(document.getElementById('Email').value)"></input><br>

<input type="submit" value="Envoyer" />

</form>

</body>

</html>

Le code JavaScript de validation est le suivant (validationFunctions.js):

function validateZipCode(data)

{

var reZip = new RegExp(/(^d{5}$)|(^d{5}-d{4}$)/);

if (!reZip.test(data))

return false;

return true;

}

function validateEmail(data)

{

var reEmail = new RegExp(/^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*$/);

if (!reEmail.test(data))

return false;

return true;

}

Soit J3Sservlet (J3S pour JavaScript Server Side) une servlet qui dessert ce formulaire. Dans la méthode doPost on définit la vérification côté serveur des données envoyées. Dans cet exemple, la servlet est codé en java. Il est possible aussi de la définir en JavaScript. Ne nous compliquons pas la tâche maintenant.

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

if (new ItemsValidatorImpl().validate(request))
….

}

La méthode validate(request) de la classe ItemsValidatorImpl permet la validation des données envoyées. Cette classe sert comme un proxy entre le code Java et le code JavaScript. Elle permet l'injection des variables utiles dans le contexte du code JavaScript côté serveur.

La définition de la classe ItemsValidatorImpl est dans le code suivant:

public class ItemsValidatorImpl extends ScriptableObject {

private static final String JS_VALIDATOR_HANDLER_FILE = "path_js_validator_handler";
private static final String JS_CONFIG_CALLED_FUNCTIONS_FILE = "path_xml_called_functions";

/**
* JavaScript Context
*/

private Context context;

/**
* Context's Scope
*/

private Scriptable scope;

private static final Log LOG = LogFactory.getLog(ItemsValidatorImpl.class);

public ItemsValidatorImpl() {

context = contextFactory.enterContext();
scope = new ImporterTopLevel(context);
scope.put("ItemsValidatorImpl", scope, this);

}

/**
* Validate Method
* @param HttpServletRequest request
* @return boolean
*/

public boolean validate(HttpServletRequest request) {

try {

Scriptable jsArgs1 = Context.toObject(request, scope);

scope.put("httpRequest", scope, jsArgs1);

String[] names = { "load", "print", "readFile" };
defineFunctionProperties(names,ItemsValidatorImpl.class,ScriptableObject.PERMANENT);

FileReader in = new FileReader(JS_VALIDATOR_HANDLER_FILE);
context.evaluateReader(scope, in, "JAVASCRIPT-CODE", 1, null);
Object result = scope.get("valid", scope);
if (result == Scriptable.NOT_FOUND) {

LOG.error("result is not defined.");
return false;

} else {

if (result.toString() == "true")

return true;

else

return false;

}

} catch (Exception ex) {

LOG.error("Exception In MyServlet.doGet :" + ex);
return false;

} finally {

Context.exit();

}

}

/**
* load Method
* @param String file
*/

public void load(String path) {

processAbsoluteFile(path);

}

/**
* print Method
* @param String msg
*/

public void print(String msg) {

System.out.println(msg);

}

/**
* readFile Method
* @param String FileName
* @return String
*/

public String readFile(String FileName) {

try {

StringBuffer fileData = new StringBuffer(1000);
BufferedReader reader = new BufferedReader(new FileReader(FileName));
char[] buf = new char[1024];
int numRead = 0;
while ((numRead = reader.read(buf)) != -1) {

String readData = String.valueOf(buf, 0, numRead);
fileData.append(readData);
buf = new char[1024];

}
reader.close();
return fileData.toString();

} catch (Exception e) {

Context.reportError("Exception in ValidatItems.readFile" + e);
return null;

}

}

private void processAbsoluteFile(String FileName) {

FileReader in = null;
try {

//Local Test
in = new FileReader(FileName);

} catch (IOException ex) {

LOG.error("Could not open file " + FileName + "".nn" + ex);
return;

}

try {

context.evaluateReader(scope, in, FileName, 1, null);

} catch (Exception e) {

Context.reportError("processAbsoluteFile Exception" + e);

} finally {

try {

in.close();

} catch (IOException ex) {

LOG.error("processAbsoluteFile Exception :" + ex);

}

}

}

// getters and setters

}

Expliquons ce code. Comme nous l'avons dit, cette classe sert à injecter l'ensemble des variables et objets utiles pour le contexte Rhino. Un contexte Rhino est un objet qui encapsule et sauvegarde les informations du thread qui exécute le code JavaScript. Un et un seul contexte est associé à un thread.

contextFactory.enterContext() permet d'instancier un context et l'associer à un thread. context.exit() permet de délibérer le contexte. Ici, et par défaut, le code JavaScript sera exécuté en mode interprété. Pour l'exécuté en mode compilé ajouter la ligne: context.setOptimizationLevel(1);

Scope permet d'enregistrer un ensemble d'objets JavaScript et de définir des niveaux d'accès. Le scope est indépendant du contexte, et plusieurs contextes peuvent accéder au même scope.

scope = new ImporterTopLevel(context); permet d'imprter le scope Top-Level (accés à tous les objets) et d'ajouter le context courant à ce scope.

scope.put("ItemsValidatorImpl", scope, this); ajoute l'istance de la classe courante au scope. La classe doit hériter de la classe ScriptableObject.

Ensuite, on ajoute l'objet request au scope:

Scriptable jsArgs1 = Context.toObject(request, scope);
scope.put("httpRequest", scope, jsArgs1);

On peut définir quelques fonctions qui seront appelées par le contexte d'exécution.
String[] names = { "load", "print", "readFile" };
defineFunctionProperties(names,ItemsValidatorImpl.class, ScriptableObject.PERMANENT);
defineFunctionProperties permet d'introspecter le code et ajoute les fonctions correspondantes au scope.

FileReader in = new FileReader(JS_VALIDATOR_HANDLER_FILE);
context.evaluateReader(scope, in, "JAVASCRIPT-CODE", 1, null);
Les deux lignes précédents ouvrent le fichier du code JavaScript et lancent son exécution.

Object result = scope.get("valid", scope); permet de récupérer le résultat de l'éxécution dans l'objet result. Le résultat de retour de l'exécution du contexte porte le nom valid.

On peut objecter que c'est trop compliqué par rapport à un simple code Java qui aurait pu faire l'affaire!!. Oui, c'est vrai, mais rappelons deux points :

  • Tout d'abord on aurait pu développer la servlet en JavaScript, ce qui permet d'éviter la définition de cette classe proxy.
  • D'autre part, il faut imaginer le cas où on a plusieurs tests de contrôle, dans ce cas, à mon avis, il est plus rapide de définir cette classe une seule fois, et utiliser le même code JavaScript que de développer le code en Java. De plus, ces classes utilitaires peuvent être définies dans le cadre d'une couche d'abstraction ou un framework à part.

Examinons maintenant le code JavaScript du contexte.

ItemsValidatorImpl.print('loading javascript');

ItemsValidatorImpl.load (jsValidatorFile);

var valid=true;

var called_functions=ItemsValidatorImp.readFile(jsConfigFile);

var xml_called_functions=new XML(called_functions);

var i=0;

var valid=true;

while (valid && i< xml_called_functions.func.length())

{

var functiontoexecute=xml_called_functions.func[i].nom;

var args=new Array();

for(var j=0; j<xml_called_functions.func[i].param.length();j++)

args.push(httpRequest.getParameter(xml_called_functions.func[i].param[j]));

valid=this[functiontoexecute].apply(this,args);

i++;

}

La première ligne montre comment appeler une fonction définie en Java. On peut aussi appeler directement la fonction par son nom.

load (jsValidatorFile);
charge le fichier contenant la définition des fonctions de test ( validationFunctions.js).

ItemsValidatorImp.readFile(jsConfigFile);
permet de lire un fichier xml qui définit l'ensemble des fonctions appelées et leurs paramètres (l'idée ici est de généraliser le test, en supposant que ItemsValidatorImp et ce code JavaScript sont définis dans un package générique, le développeur n'a que déclarer les noms des fonctions JavaScript de vérification et leurs paramètres dans un fichier XML).

Le reste du code parcourt le fichier xml et exécute le les fonctions déclarées (E4X supporté).

Un commentaire

  1. I have been exploring for a little bit for any high-quality articles or weblog posts in this sort of area . Exploring in Yahoo I finally stumbled upon this site. Studying this information So i’m glad to express that I’ve an incredibly just right uncanny feeling I discovered just what I needed. I such a lot indisputably will make sure to don?t disregard this web site and give it a look on a continuing basis.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.