Accueil » Java

JBoss Tohu et GateIn : construire des formulaires dynamiques

24 février 2010 1 853 affichages 4 commentaires

GateIn

L’actualité eXo Platform

GateIn est un moteur dont je vous ai déjà parlé l’an dernier (ici et surtout là), et qui revient sur le devant de la scène. Avec une sortie finale prévue début mars, l’année 2010 sera eXo Platform ou ne sera pas. En octobre dernier, Sacha Labourey que vous avez vu à l’anniversaire du Paris JUG, ancien directeur général de JBoss Europe a rejoint le board. C’est aussi Edwin Khodabakchian, précédemment vice-président du développement produits d’Oracle, et Bob Bickel, co-fondateur de Bluestone Software qui eux aussi s’associent à Benjamin Mestrallet.

Il y aura très certainement encore quelques visages connus de la communauté Java dans les semaines qui viennent. Bob Bickel sur Twitter le 21 février dernier lâche un énigmatique message :

Exo just added another key person to join the team. Another couple me weeks before we announce though.

JBoss Tohu

A la recherche de nouvelles idées pour tester GateIn, j’ai travaillé sur un prototype avec JBoss Tohu et GateIn CR1. En quelques mots, JBoss Tohu est un moteur de génération de questionnaires basés sur Drools. Il permet de proposer des questionnaires dont la liste est déterminée par les réponses des utilisateurs aux réponses précédentes. C’est avant tout un moteur orienté Web, qui permet de créer des interfaces de saisies avec peu de lignes de code. Capable de s’intégrer avec Seam, JSF ou Spring MVC, c’est un moteur simple qui permet de gagner du temps.

Dans la démonstration que je vous ai préparé, vous verrez comment je génère une nouvelle version du formulaire en éditant simplement le fichier Drools de définition du questionnaire.

En terme de performances, c’est un peu poussif. J’ai dû aussi adapter les paramètres de mémoire de GateIn car Drools+Tomcat n’aime pas spécialement le redéploiement à chaud. Mais à part cela, c’est très facile comme nous allons le voir.

GateIn CR1

La dernière version de GateIn supporte 15 langues, l’intégration du moteur JCR 1.12.0-Beta06, un grand nombre de corrections et d’améliorations. En terme de performances, j’ai testé la version Tomcat et il y a une amélioration sensible du temps de démarrage et des performances. Je n’ai eu qu’à relancer une version du mois d’octobre pour voir la différence.

Rappelons que GateIn est un moteur d’applications, un socle applicatif Web. Ce n’est pas uniquement un Portail, c’est un socle technique qui permet de passer la seconde. Prenez un serveur comme Tomcat, auquel vous êtes habitué. Il vous donne un certain nombre de fonctionnalités, comme la gestion des connexions avec une base, de quoi l’administrer facilement et de gérer le clustering par exemple. GateIn est une porte d’entrée vers le Web 4.0 avec un moteur d’exécution capable certes de faire tourner une webapp, mais aussi de proposer un moteur événementiel du côté navigateur, un serveur de Google Gadget, un JCR, un système de gestion des autorisations et de l’authentification, un moteur de template basé sur Groovy, bref un nombre de fonctionnalités intéressant pour qui développe des applications de nouvelles générations.
Ce sera un socle sur lequel il est prévu d’intégrer et de faire collaborer des applications que l’on connaît comme Alfresco, Bonita ou Nuxeo. L’une des cibles est Microsoft SharePoint. La suite logicielle de Microsoft dispose d’un moteur de recherche, d’une gestion documentaire, de forums et de la possibilité de créer des formulaires dynamiques.

Et c’est exactement ce que je vais faire avec JBoss Tohu : créer un formulaire dynamique et capturer les réponses de l’utilisateur pour générer un PDF.

Vidéo de l’application

J’ai mis en ligne sur YouTube une vidéo du projet complet. Après avoir lancé GateIn, je charge une page sur laquelle j’ai déclaré en quelques lignes un formulaire. Notez que le formulaire s’adapte dynamiquement selon les réponses sélectionnées par l’utilisateur. Lorsque je clique sur « Done » une Servlet génère un fichier PDF avec iText, votre certificat de meilleur Framework Web.

Le code de la page principale de la Portlet est très simple. J’ai simplement adapté le chargement des ressources comme jQuery à GateIn, mais pour le reste, l’essentiel du travail est effectué par JBoss Tohu. Au chargement de la page, jQuery récupère via une Servlet spéciale la liste des questions, et construit à la volée la page. A chaque fois que le visiteur rempli une valeur, une règle Drools est évaluée. Il est alors possible de déclencher l’affichage de nouvelles zones automatiquement selon les réponses données par l’utilisateur. Imaginez ce système appliqué au monde de l’assurance ou de la finance, pour générer des contrats à la volée par exemple.

<%@ page import="java.util.List" %>
<%@ page import="javax.portlet.Portlet" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>


<portlet:defineObjects/>
<link rel="stylesheet" type="text/css" media="screen, projection" href="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/css/ui.base.css" />
<link rel="stylesheet" type="text/css" media="screen, projection" href="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/css/ui.core.css" />
<link rel="stylesheet" type="text/css" media="screen, projection" href="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/css/ui.theme.css" />
<link rel="stylesheet" type="text/css" media="screen, projection" href="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/css/ui.datepicker.css" />
<link rel="stylesheet" type="text/css" media="screen, projection" href="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/css/jquery.readonly.css" />
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/ui.core.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/ui.datepicker.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/cookie.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/sprintf.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/toxml.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/jquery.readonly.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/jquery/jquery.url.packed.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/dynamicUI_messages.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/dynamicUI_utility.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/dynamicUI_ajax.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/dynamicUI_objects.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/dynamicUI_interface.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/dynamicUI_html.js"></script>
<script type="text/javascript" src="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/script/dynamicUI_main.js"></script>

<link rel="stylesheet" type="text/css" media="screen, projection" href="<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/css/style.css" />



<script type="text/javascript">
$(document).ready(function() {
    setDroolsURL("<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/stateful/simple");
    setActionURL("<%=renderResponse.encodeURL(renderRequest.getContextPath())%>/action/");
  onQuestionnaireLoad("bodyContent", "simple");
});
</script>

<div id="content">

<div id="bodyContent"> <p class="preLabel">Fill-in the form and click on "Done" to generate your certificate as PDF.</p></div>

</div>
<div class="copyright">
Copyright(c) 2010 Nicolas Martignole - GateIn and JBoss Tohu</div>

Le nom du formulaire à appeler est spécifé dans la fonction jQuery onQuestionnaireLoad. Il serait facile de prévoir une Portlet GateIn générique et de spécifier cette valeur comme propriété de la Portlet par exemple.

Le fichier Drools

Le script Drools est éditable bien entendu avec les outils graphiques de Drools. Vous allez voir qu’il est simple de construire un enchaînement de questions dynamiques.

package com.innoteria.docxang

import java.util.Calendar;

import org.tohu.Group;
import org.tohu.Question;
import org.tohu.Questionnaire;
import org.tohu.MultipleChoiceQuestion;
import org.tohu.MultipleChoiceQuestion.PossibleAnswer;
import org.tohu.Note;

/*
 * Defines the questionnaire and initial questions which always appear.
 * Note that this rule has no when clause so it fires once when the session is first created.
 */
rule "static"
dialect "mvel"
then
	Questionnaire questionnaire = new Questionnaire("myQuestionnaire");
	questionnaire.setLabel("GateIn and JBoss Tohu Sample");
	// items (inherited from Group) determines the order in which things are displayed
	questionnaire.setItems({"name", "hello", "webFrameworkType", "otherWebFrameworkType", "description"});
 	// completionAction specifies what to do when the user completes the questionnaire (i.e. clicks the Done button).
 	// The JQuery client interprets this as uri but other clients could use it in other ways.
	questionnaire.setCompletionAction("generatedCertificate.pdf");
	insertLogical(questionnaire);

	Question question = new Question("name");
	question.setAnswerType(Question.TYPE_TEXT);
	question.setPreLabel("What's your name?");
	insertLogical(question);

	MultipleChoiceQuestion multi = new MultipleChoiceQuestion("webFrameworkType");
	multi.setAnswerType(Question.TYPE_TEXT);
	multi.setPreLabel("What type of Web Framework do you like?");
	multi.setPossibleAnswers({
		new PossibleAnswer(null, "None"),
		new PossibleAnswer("wicket", "Apache Wicket"),
		new PossibleAnswer("tapestry", "Apache Tapestry"),
		new PossibleAnswer("jsf", "Java Server Faces"),
		new PossibleAnswer("seam", "Seam"),
		new PossibleAnswer("other", "Other")
	});
	insertLogical(multi);
end

/*
 * If the user provides their name then we create a new note called hello.
 * If they blank out their name then the note will disappear again automatically because of truth-maintenance.
 */
rule "hello"
when
	Question(id == "name", answered == true);
then
	Note note = new Note("hello");
	insertLogical(note);
end

/*
 * The actual text for the hello note is in a separate rule because we need it to fire whenever the name is changed,
 * not just when we create the note (which only fires when the name is first specified).
 */
rule "hello data"
no-loop
when
	Question(id == "name", name : answer);
	hello : Note(id == "hello");
then
	hello.setLabel("Hello " + name + "!");
	update(hello);
end

/*
 * This dependent question only appears for a specific data value. i.e. webFramework type is Other.
 * It will disappear automatically via truth-maintenance.
 */
rule "other type of web framework"
when
	Question(id == "webFrameworkType", answer == "other");
then
	Question question = new Question("otherWebFrameworkType");
	question.setAnswerType(Question.TYPE_TEXT);
	question.setPreLabel("Please specify wich Web Framework:");
	insertLogical(question);
end

/*
 * This dependent question appears whenever the original question is non-null.
 * It will disappear automatically via truth-maintenance.
 */
rule "webframework's name"
when
	Question(id == "webFrameworkType", answered == "true");
then
	Question question = new Question("description");
	question.setAnswerType(Question.TYPE_TEXT);
	question.setPreLabel("Can you explain why it's your favorite framework?");
	insertLogical(question);
end

J’ai copié le fichier complet, ce qui est un peu verbeux mais qui vous permet de comprendre comment fonctionne JBoss Tohu. A partir de ces règles Drools, Tohu est capable de générer une structure XML qui représente le Questionnaire et les Questions. jQuery charge dans le navigateur les questions, et retourne à une Servlet de Tohu les réponses. Lorsque l’utilisateur soumet le formulaire, vous recevez simplement une Map clé/valeur. A vous ensuite d’en faire ce que vous voulez. J’ai adapté l’un des exemples de Tohu qui génère un fichier PDF avec iText.

Conclusion

L’intéret de GateIn est de mettre en place rapidement une application avancée, avec déjà de l’authentification et la gestion du cycle de vie de l’application. L’idée pour aller plus loin et vraiment « faire du GateIn » est la suivante : générer un PDF, le stocker dans le JCR et le rendre accessible via CMIS. En effet, les équipes d’eXo Platform ont annoncé la disponibilité d’un connecteur CMIS sous la forme d’un projet LGPL, xCMIS.

Enfin ne loupez pas le blog de Jérémi Joslin, ancien responsable produit de la partie eXo Social, qui poste des billets très intéressants sur GateIn.

Ressources
La page JBoss Tohu
La page GateIn.org
Une vidéo YouTube d’exemple de JBoss Tohu

4 commentaires »

  • zepouet a dit:

    Excellente plus value de GateIn !

  • Christian a dit:

    Intellectuellement, tout cela est séduisant.
    Mais ca fait quand même beaucoup penser à une grosse usine à gaz que l’on pourrait aisément remplacer par qq lignes de rails ou php.
    Je suis le seul à qui ça fait cet effet ?

  • Florent Ramière a dit:

    @Christian
    J’ai eu la même impression que toi.
    En fait, dès que l’on dégaine un moteur de règles pour faire quoi que ce soit c’est que l’on a un besoin *réellement* non trivial.
    Dans ce cas, ce n’est pas le langage qui va permettre de t’aider au mieux pour implémenter ton besoin, mais bien le moteur de règle.
    Ensuite, comme d’habitude, c’est coton pour tester.

    Nicolas, tu aurais des pointeurs sur des use case adaptés a cet outil ?

  • Nicolas Martignole a dit:

    @Florent : oui j’ai un cas d’usage en tête : générer des interfaces de saisie dynamiquement pour ensuite éditer des documents contractuels comme des Contrats ou des Devis. Le moteur de règle permet ici de gérer la combinatoire, d’extraire l’intelligence du coeur du produit. Je reconnais le côté « Usine à Gaz ». Je sors de l’habituelle application de gestion, que j’aurai habillé avec du Grails ou autre, pour aller chercher des sujets un peu plus touchy.
    En fait peu importe de savoir si cela va servir ou non. L’important est de montrer qu’en quelques heures, il a été possible de créer une application qui sort de l’habituel « Hello World », grâce à ces outils.