vendredi 1 avril 2011

«Git, dessine moi un mouton»

Il y a quelques mois je découvrai Git, un gestionnaire de sources distribué dont la réputation croît en ce moment. Ayant déjà vécu la montée en puissance de Subversion au détriment de CVS, je voulais me rendre compte des qualités/défauts du nouveau challenger.

Une nouvelle philosophie


Git fait partie de la famille des contrôleurs de sources distribués. Cela se traduit principalement par le fait que chaque membre d'une équipe collaborant à un même projet est en possession d'un dépôt contenant l'ensemble de l'historique du projet. Les publications se font ainsi en local, ne nécessitant alors aucune connectivité réseau. Seul les échanges entre dépôts se font via le réseau.
Une première question vient alors à l'esprit : Si tout le monde est un dépôt, d'où provient la version de l'application à livrer ? Plusieurs organisation sont possibles à ce sujet:

  • Une organisation par cercle de confiance, comme par exemple pour le développement du noyau Linux, où un membre de l'équipe représente le dépôt de référence que tout le monde utilise pour compiler sa version du noyau, alimenté par les dépôts de plusieurs lieutenant possédant chacun un dépôt alimenté par des dépôts considérés par ses derniers comme étant de confiance.

  • L'outil est suffisament souple pour reproduire les mécanismes des dépôts centralisés. Un dépôt sur un serveur sert de référence, utilisé par exemple par un automate de compilation permettant d'assurer une intégration continue des sources du projet, les développeurs de l'équipe déposent alors des lots de publication sur ce dépôts lors de chaque instruction git push

  • et bien d'autres ...


Une autre question suvient : Comment se joindre à un projet ? Il suffit tout simplement de cloner un dépôt avec la commande git clone /jango/fet.git et vous voilà avec un clone tout neuf, puis de commencer les développement. C'est un peu comme si chaque développeur avait forker le projet afin de produire une version. L'ensemble des développements publiés dans un dépôt représente alors un patch à appliquer aux autres dépôts pour contribuer au développement.

Une migration en douceur


Lors de la migration de CVS vers Svn, un utilitaire fournit (cvs2svn) permettait de migrer un dépôt CVS en dépôt Subversion tout en conservant l'historique. Une action one shot qui implique de synchroniser l'ensemble des membres de l'équipe utilisant le dit dépôt vers le nouvel outil.
Permier avantage de Git sur Svn : git-svn. Cet utilitaire fait la même chose que le précédent, c'est-à-dire de constituer un dépôt Git à partir d'un dépôt Subverion tout en conservant l'historique via la commande git svn clone. Mais il va plus loin en conservant une connectivité avec le dépôt Subversion lui servant d'origine. Ainsi le dépôt git et maintenu à jour en continu avec la commande git svn rebase.
Cela va encore plus loin en permettant de publier les commit effectués dans le dépôt Git vers le dépôt Subverion via la commande git svn dcommit. De cette manière, un développeur peut se mettre à utiliser Git tout en continuant de collaborer à un projet dont le référentiel de source est Subversion (ce que je fit pendant plusieurs mois).

Un bénéfice au quotidien


«Git est un nouvel outil!
- d'accord
-Il est plus fashion!
- OK, mais encors?»

«Wiiiilsooooon !!!!»


On peut aussi tout simplement utilser Git en mode seul au monde pour historiser tout ce qui se trouve sur un ordinateur. La création d'un dépôt se fait avec la commande git init, puis ont ajoute les fichiers à historiser via la commande git add monprecieux.fic puis on plublie les modification via la commande git commit. Pas besoin d'un serveur où d'un dépôt à créer et/ou administrer. C'est simple et efficace et l'on peut s'en servir pour maintenir l'historique des fichiers de configuration sur un serveur (<troll>bein oui un serveur Linux/UNIX, comment on ferait pour historiser les clics droits sous Windows®</troll>) et tout projet sandbox commencé mais jamais terminé. Je m'en sert également pour réaliser des kit de démarrage afin de savoir toutes les étapes que j'ai franchies et ainsi produire une documentation exhaustives.

«Release Early, Release Often»


Ce principe de livraison fréquente peut être appliquer au niveau du développeur qui va pouvoir publier fréquement ces modifications induisant ainsi une meilleur qualité de ces publications (fini le fichiers qui manquent, les modifications oubliées dans un coin ne sachant pas si elles concernaient la tâhes en cours, ...). Chaque modification faisant progresser le code d'un état stable à un autre (bien qu'intermédiaire) doit faire peut faire l'objet d'un commit ce qui n'est pas envisageable sur un projet centralisé.

Un build incassable


Cette capacité à pouvoir publier sans retenue permet la mise en oeuvre du build incassable. Dans la mesure où seul le dépôt local est touché par ces modifications une sécurité m'est offerte en clonant mon propre dépôt dans un endroit de mon ordinateur et en construisant mon projet à partir de ce clone afin de constater la fiabilité des mes développements avant le partage de mes travaux.

«cou roucoucou roucoucou stach stach»


Il y a des moments où le travail suit son cours, le soleil est radieux, la journée a bien commencé, quand tout à coup le ciel s'obscurci, un bug en prod doit être fixé de toute urgence sinon c'est la fin du monde. Avec SVN il me fallait alors créer une nouvelle copie de travail (ce qui peut être long sur de très gros projet) puis la supprimer une fois l'urgence traitée. Où alors de créer un patch, annuler toutes mes modifications en cours puis appliquer le patch après la tempête.
La commande git stash permet ainsi de mettre de côté (to stash = planquer) les modifications en cours pour repartir d'une version clean de la copie de travail puis de reprendre les travaux au moyen de la commande git stash pop, tout ceci avec une efficacité sans précédent pour moi.

Conclusion


En conclusion, je n'ai pas listé beaucoup de défauts (aucun pour être précis) mais je noterai tout de même l'effort à produire pour changer d'habitude et notamment le changement de philosophie induit par la distribution des dépôts.
Je m'y met de plus en plus serieusement ce qui implque d'autres publications à l'avenir sur cet outil qui fait maintenant parti de mes meilleurs amis en tant que développeur.

jeudi 31 mars 2011

Une messagerie instantannée ... en JEE 6

Durant ma formation universitaire il m'a été donné plusieurs fois l'occasion de programmer des applications orientées réseau.
Tout d'abord en C afin d'apprendre l'utilisation des Sockets et d'illustrer les cours de réseaux, puis par la suite en Java pour mettre en oeuvre la programmation multi-Thread avec une interface graphique basique.
Le thème récurrent de tous ces exercices est le cas d'une messagerie instantanée permettant l'échange de messages entre différents interlocuteurs.
On ne change pas une technique qui gagne, je remet une pièce dans la machine pour illustrer cette fois une implémentation en ... XHTML/JavaScript reposant sur un serveur Java et une Servlet asynchrone.

La théorie


En théorie rien de bien compliqué:

Le client

Une partie de l'application responsable de l'envoi du commentaire de chaque utilisateur à destination du serveur et en même temps à l'écoute des commentaires en provenance du serveur

Le serveur

L'autre partie de l'application recevant les commentaires de chaque client et renvoyant ces commentaires à l'ensemble des clients connectés


La pratique


En C puis en Java, la technique est assez similaire et consiste à ouvrir une socket client->serveur permettant l'envoi des commentaires ainsi qu'une autre socket serveur->client permettant la restitution des commentaires à chaque client. On pouvait envisagé également l'utilisation des IP de broadcast pour simplifier l'architecture. Mais en JEE, comment implémenter tout cela ?

Le serveur


Une solution de base est trouvée tout simplement avec l'apparition des traitements asynchrones des requêtes proposé par les Servlets Java à partir de la spécification 3.0. Cette Servlet traitera donc de manière asynchrone les requêtes de type GET pour mettre les clients en attente de commentaires, ces derniers étant transmis à la même Servlet par des requête de type POST.

La réception de message


Pour recevoir les messages, la Servlet propose une implémentation de la méthode doPost(...), cette dernière recevant le commentaire d'un client le transmet aussitôt à l'ensemble des clients en écoute via une méthode fireMessage(...) décrite plus bas.

/**
* On répond au POST en transmettant les informations à tous les clients en attente.
*/
@Override
protected void doPost( HttpServletRequest req, HttpServletResponse resp )
throws ServletException, IOException
{
String nickname = req.getParameter( "nickname" );
String message = req.getParameter( "message" );
fireMessage( new PostData( nickname, message ) );
}

Des clients en écoute ?


Et oui, ces derniers on quand à eux fait un appel GET et doivent être en attente, c'est donc la méthode doGet(...) qui fait cela en appelant la méthode startAsync sur l'objet request transmis à la Servlet. Cet appel retourne une instance de AsyncContext que nous allons conserver précieusement dans une file d'attente.

/**
* Un client se tient informé des nouveaux messages, on le place en file d'attente.
*/
@Override
protected void doGet( HttpServletRequest req, HttpServletResponse resp )
throws ServletException, IOException
{
if ( req.isAsyncSupported() )
{
AsyncContext context = req.startAsync();
context.setTimeout( 1 * MINUTE );
contexts.add( context );
}
else
{
log( "Les requêtes asynchrones ne sont pas supportées, dommage!" );
}
}

La notification des clients


Il est temps maintenant de prévenir les clients en attente du dernier message reçu:

/*
* Envoi les message aux clients en attente.
*/
private void fireMessage( PostData postData )
throws IOException
{
while ( !contexts.isEmpty() )
{
AsyncContext context = contexts.poll();
ServletResponse response = context.getResponse();
assert response != null;
response.setContentType( "text/json" );
Writer out = response.getWriter();
String json = postData.toJSON();
out.write( json );
out.flush();
context.complete();
}
}

Dans cette méthode comme dans la méthode doGet(...), on fait appel à un membre nommé contexts. J'ai choisit ici de mettre les clients dans une file d'attente protégée contre les accès concurrents.

/* Ensemble des requétes asynchrones en attente de réponse. */
private Queue<AsyncContext> contexts = new ConcurrentLinkedQueue<AsyncContext>();

Le client


Maintenant que notre serveur est prêt, il convient d'implémenter un client. Pour respecter l'emploi des fondamentaux de cet article, c'est donc une page Web en XHTML/JavaScript qui fera office de client au moyen du navigateur. Cette page devra être capable d'envoyer les commentaires de l'utilisateur et en même temps d'afficher les commentaires reçus.
C'est une technique connu sous le nom d'AJAX qui permettra de gérer l'envoi / reception de messages. J'ai choisit d'utiliser jQuery afin de simplifier son utilisation mais il aurait été tout à fait possible de coder tout cela avec une belle XmlHTTPRequest.

L'envoi d'un commentaire


Pour saisir le commentaire, rien de mieux qu'un petit formulaire.

<div>
<label for="nickname">Pseudo</label><input id="nickname" type="text" value="" tabindex="1" />
<input id="send" type="button" value="Envoyer" tabindex="3" />
</div>
<textarea id="message" rows="5" cols="40" tabindex="2"></textarea>

Puis un peu de JavaScript avec jQuery.

$(document).ready(function() {//onload de la page
$("#send").click(function() {//onclick sur le bouton send
sendMessage($("#nickname").val(), $("#message").val());
});
});
function sendMessage(nickname, message) {
$.post("chat", {
nickname : nickname,
message : message
}).success(function() {
$("#message").val('');
}).error(function() {
alert('NOGO : failure sendind data');
});
}

La réception d'un commentaire


La réception d'un commentaire commence dès le chargement de la page.

$(document).ready(function() {//onload de la page
...
tryToPull();
});
function tryToPull() {
$.ajax({//long polling
url : "chat"
}).success(function(data) {
addMessage(data);
}).error(function() {
alert('Error!');
});
}
function addMessage(data) {
try {
if (data.nickname && data.message) {
$("#messages").html("
" + data.nickname + "
" + data.message + "
");
}
tryToPull();
} catch (e) {
alert(e.message);
}
}

Les données reçues sont au format JSON comme indiqué par le code de la Servlet. jQuery reçoit donc un flux JSON qui devient un objet JavaScript, le fameux data de la méthode addMessage(...)

Conclusion


Une fois l'application chargée dans Tomcat 7 : TADA ! Le chat a encore frappé, voici une illustration de l'utilisation des Servlet 3.0 pour le traitement asynchrone des requêtes.
Remarque n°1: Il faut utiliser 2 navigateurs différents pour être sûr d'avoir 2 clients.
Remarque n°2: Je n'ai pas écrit de fichier web.xml car c'est devenu optionnel avec JEE 6.

jeudi 10 février 2011

MVP4G Spring et GWT

Parmis les plateformes de développement de clients riches (chaînon manquant entre client lourd et client léger), 2 sont en forte concurrence : Google Web Toolkit(alias GWT) et Adobe Flex.

Le but ici n'est pas de traiter qui est le meilleur, mais de faire un focus sur la mise en oeuvre de GWT dans le cadre d'une application métier complexe et critique. Dans ce sens, la grande quantité de composants dans l'application ainsi qu'une maintenance évolutive constante ont conduit à utiliser un framework permettant maîtriser les efforts de maintenance. Ce choix c'est porté sur le framework MVP4G.

Voici comment développer une application GWT reposant sur ce framework.

Instant zéro : les outils


Les outils utilisés dans le cadre de cette démarche sont:

Maven 3

Le couteau suisse par excellence

Eclipse Helios

Un des plus puissants environnements de développement gratuits

GWT

Le kit de développement GWT ainsi que le plugin pour Eclipse


Je ne détaillerai pas ici l'installation de ces outils, on se rendra sur les sites dédiés pour en savoir plus.

The Bigbang Theory


Boom!


Maintenant que les outils sont prêts, l'heure est venue de créer notre projet. Nous utiliserons maven qui dispose d'un plugin permettant de gérer les projets GWT.

> mvn archetype:generate

Cette commande vous propose de choisir le modèle de projet à générer parmi la pléthore d’archétypes disponibles. Il faut chercher dans cette liste le type de projet "gwt-maven-plugin" qui porte le numéro 235 lors de l'écriture de ces lignes. On choisira par la suite le numéro de version à utiliser (la dernière par défaut) puis l'assistant va demander quelques informations sur le projet : groupId, artifactId, version, ... et le nom du module GWT. Ce dernier donnera une classe <NOM_DU_MODULE>.java héritant de EntryPoint puis un ficher <NOM_DU_MODULE>.gwt.xml.

Premier run dans Eclipse


Nous venons de mettre au monde notre projet en quelques secondes, maintenant il convient de lui faire faire ses premiers pas. Pour cela on se rendra dans le dossier construit par maven que nous noterons par la suite <artifactId>.

> cd <artifactId>
> mvn eclipse:eclipse

On importera le projet dans Eclipse via Import ... / Existing project.
Il est possible que le fichier pom.xml ne soit pas tout à fait propre, j'ai entre-autre purifié son contenu pour prendre en compte la version Java 6 des sources en lieu et place de Java 5, puis customisé un peu la configuration du plugin maven-eclipse-plugin avant de relancer la commande précédente pour obtenir un projet "propre".
Pour lancer le projet dans Eclipse, un simple clic droit Run as ... / Web application et voila notre projet qui tourne. Il s'agit du projet de base créé également lorsque l'on utilise l'assistant Eclipse, le site officiel GWT en dit plus sur ce projet dans le guide du débutant.

Un peu de Spring


Le projet met en oeuvre un service GWT RPC afin d'illustrer ce fonctionnement. Ces services RPC sont des servlets à déclarer dans le ficher web.xml. Dans le cadre d'une application à grande échelle, une grande quantité de services vont être développés afin de convenir aux spécifications du métier. Dans ce sens il convient d'améliorer la productivité sur le développement et la mise en oeuvre de ces services. Le plus simple étant de laisser le framework Spring instancier ces services et l'on disposera alors d'une seule et unique servlet dispatchant les appels RPC au beans construits pour assurer l'implémentation de ces services.

"Dit à Maven de dire à Eclipse ..."


Première étape, on modifie le fichier pom.xml pour ajouter la dépendance vers les bibliothèques nécessaires:

<project>
...
<properties>
<springVersion>2.5.6.SEC02</springVersion>
<properties>
...
<dependencies>
...
<!-- Spring 4 GWT -->
<dependency>
<groupId>com.google.code</groupId>
<artifactId>spring4gwt</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${springVersion}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
...
</dependencies>
...
</project>

On met à jour le projet via la commande magique pour ajouter automatiquement les librairies indiquées dans Eclipse:

> mvn eclipse:eclipse

... puis Refresh ou F5 dans Eclipse et c'est parti!

Refactor


Notre projet n'a pas beaucoup changé, il est juste plus gros car embarque désormais plusieurs JAR supplémentaires.

applicationContext.xml


On ajoutera un fichier de description des beans Spring en prenant soin de faire appel à une fonction pratique : le scan automatique de composants. Ce fichier sera déposé dans le dossier src/main/resources du projet:

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan
base-package="PAQUETAGE_DE_BASE_DE_MON_PROJET.server" />
</beans>

web.xml


Nous allons mettre à jour le descripteur de l'application afin de remplacer la Servlet existante par une Servlet un peu plus dynamique. Supprimer les lignes suivantes du fichier web.xml se trouvant dans src/main/webapp/WEB-INF/

<web-app>
...

<!-- Servlets -->
<servlet>
<servlet-name>greetServlet</servlet-name>
<servlet-class>PAQUETAGE_DE_BASE_DE_MON_PROJET.server.GreetingServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>greetServlet</servlet-name>
<url-pattern>/NOM_DE_MON_MODULE_GWT/greet</url-pattern>
</servlet-mapping>

...
</webapp>

... puis nous ajouterons les lignes suivantes:

<webapp>
...
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- SpringGwt remote service servlet -->
<servlet>
<servlet-name>springGwtRemoteServiceServlet</servlet-name>
<servlet-class>org.spring4gwt.server.SpringGwtRemoteServiceServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springGwtRemoteServiceServlet</servlet-name>
<url-pattern>/NOM_DE_MON_MODULE_GWT/services/*</url-pattern>
</servlet-mapping>

<!-- Spring servlet filter -->
<filter>
<filter-name>springRequestFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>springRequestFilter</filter-name>
<servlet-name>springGwtRemoteServiceServlet</servlet-name>
</filter-mapping>
...
</webapp>

"Sinon le refactor c'est quand ?"


Nous allons mettre à jour le code du service en commençant par son interface. L'annotation permettant à GWT de connaître une partie de l'URL de ce dernier doit être mise à jour. Dans le fichier GreetingService.java l'annotation prendra la valeur suivante:

@RemoteServiceRelativePath("services/greet")

Le changement majeur sera pour l'implémentation du service. Jusqu'à présent, la classe GreetingServiceImpl héritait de RemoteServiceServlet, une classe propre à GWT. Nous allons briser cet héritage transformant ainsi la classe en un java bean classique.

...
@Service("greet")
public class GreetingServiceImpl
implements GreetingService {
...
}

Notre classe ne compile plus car elle faisait appel à des méthodes de RemoteServiceServlet afin d'accéder à des propriétés de la requête. Voici le nouveau code que nous allons mettre en place pour palier cette lacune:

...
HttpServletRequest request = ((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest();
String serverInfo = request.getSession().getServletContext().getServerInfo();
String userAgent = request.getHeader("User-Agent");
...

Le refactor est maintenant terminé, on relance le serveur et on constate que l'application fonctionne comme avant. Dingue!

MVP4G


Nous rentrons maintenant dans le vif du sujet, nous pouvons ajouter autant de services que nous voulons sans trop d'efforts, faisons de même pour l'écran.

"Dit aussi à Maven de dire à Eclipse ..."


Nous ajoutons maintenant la dépendances nécessaire à l'utilisation du framework MVP4G dans le fichier pom.xml:

<project>
...
<dependencies>
...
<!-- MVP design pattern 4 GWT -->
<dependency>
<groupId>com.googlecode.mvp4g</groupId>
<artifactId>mvp4g</artifactId>
<version>1.2.0</version>
</dependency>
...
</dependencies>
...
</project>

... puis la commande magique (voir plus haut), maintenant nous sommes prêts.

Definition du module GWT


Nous ajouterons un lien vers le module GWT MVP4G via le descripteur de notre module GWT NOM_DE_NOM_MODULE_GWT.gwt.xml:

<module>
...
<!-- Inherit the Mvp4gModule stuff. -->
<inherits name='com.mvp4g.Mvp4gModule' />
...
</module>

Le présentateur


Pierre angulaire du pattern MVP, nous allons produire un présentateur reproduisant le comportement actuel de notre application :

/**
* Presenter de salutation.
*/
@Presenter(view = GreetView.class)
public class GreetPresenter
extends LazyPresenter<IGreetView, RootEventBus>
{
/**
* Specification d'une vue servant a la salutation.
*/
public static interface IGreetView
extends LazyView
{
/** Composant permettant la saisie du nom. */
HasValue<String> getUsername();

/** Indication d'une erreur éventuelle. */
HasText getErrorLabel();

/** Bouton soumettre. */
HasClickHandlers getButtonSend();

/** Désactive le bouton de soumission. */
void disableButtonSend();

/** Active le bouton de soumission. */
void enableButtonSend();
}

@Override
public void createPresenter() {
}

@Override
public void bindView() {
view.getButtonSend().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
final String username = view.getUsername().getValue();
view.getErrorLabel().setText("");
if (!FieldVerifier.isValidName(username)) {
view.getErrorLabel().setText("Please enter at least four characters");
} else {
view.disableButtonSend();
greetService.greetServer(username, new AsyncCallback<String>() {
@Override
public void onSuccess(String result) {
greetSucceed(username, result);
}

@Override
public void onFailure(Throwable caught) {
greetFailed(username, caught);
}
});
}
}
});
}

/**
* @see RootEventBus#start()
*/
public void onStart() {
// Nothing special to do here.
}

protected void greetSucceed(String username, String result) {
final Label textToServerLabel = new Label(username);
final HTML serverResponseLabel = new HTML(result);
VerticalPanel widget = new VerticalPanel();
widget.addStyleName("dialogVPanel");
widget.add(new HTML("<b>Sending name to the server:</b>"));
widget.add(textToServerLabel);
widget.add(new HTML("<br><b>Server replies:</b>"));
widget.add(serverResponseLabel);
widget.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
showPopup("Remote Procedure Call", widget);
}

protected void greetFailed(String username, Throwable caught) {
final Label textToServerLabel = new Label(username);
final HTML serverResponseLabel = new HTML("An error occurred while "
"attempting to contact the server. Please check your network " "connection and try again.");
serverResponseLabel.addStyleName("serverResponseLabelError");
VerticalPanel widget = new VerticalPanel();
widget.addStyleName("dialogVPanel");
widget.add(new HTML("<b>Sending name to the server:</b>"));
widget.add(textToServerLabel);
widget.add(new HTML("<br><b>Server replies:</b>"));
widget.add(serverResponseLabel);
widget.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
showPopup("Remote Procedure Call - Failure", widget);
}

private void showPopup(String title, Widget widget) {
final DialogBox popup = new DialogBox();
popup.setText(title);
VerticalPanel container = new VerticalPanel();
container.add(widget);
Button closeButton = new Button("Close");
closeButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
popup.hide();
view.enableButtonSend();
}
});
container.add(closeButton);
popup.add(container);
popup.center();
}

@Inject
private GreetingServiceAsync greetService;
}

La vue


Notre code ne compile pas pour le moment car il nous manque le complice du présentateur : la vue. Nous allons maintenant lui donner vie.

/**
* Sample Greet view.
*/
public class GreetView
extends Composite
implements IGreetView
{
@Override
public HasValue<String> getUsername() {
return username;
}

@Override
public HasText getErrorLabel() {
return errorLabel;
}

@Override
public HasClickHandlers getButtonSend() {
return buttonSend;
}

@Override
public void disableButtonSend() {
buttonSend.setEnabled(false);
}

@Override
public void enableButtonSend() {
buttonSend.setEnabled(true);
}

/**
* @see com.mvp4g.client.view.LazyView#createView()
*/
@Override
public void createView() {
DockLayoutPanel container = new DockLayoutPanel(Unit.PX);
Widget title = new HTML("<h1>Web Application Starter Project</h1>");
container.addNorth(title, 140);
FlowPanel content = new FlowPanel();
username = new TextBox();
content.add(username);
buttonSend = new Button(messages.sendButton());
content.add(buttonSend);
errorLabel = new Label();
errorLabel.addStyleName("serverResponseLabelError");
content.add(errorLabel);
content.addStyleName("content");
container.add(content);
this.initWidget(container);
this.addStyleName("greetView");
}

private TextBox username;

private Label errorLabel;

private Button buttonSend;

@Inject
private Messages messages;
}

Le car ... non le bus!


La colonne vertébrale de notre application est un bus d’évènements permettant de connecter entre eux l'ensemble des composants de notre application à la manière d'une colonne vertébrale.

/**
* Bus d'événement global à l'ensemble de l'application.
*/
@Events(startView = GreetView.class)
public interface RootEventBus
extends EventBus
{
/**
* Premier événnement lancé au démarrage de l'application.
*/
@Start
@Event(handlers=GreetPresenter.class)
void start();
}

"Et maintenant on refactor ?"


Le point d'entrée d'une application est la classe qui étend EntryPoint, celle automatiquement générée lors de la création du projet concentre l'ensemble du comportement de l'application, ce que nous avons distribué dans le présentateur et la vue. Nous pouvons donc supprimer l'ensemble du contenu initial pour mettre le code suivant :

/**
* Point d'entrée du module GWT.
*/
public class NomDeMonModule
implements EntryPoint
{
/**
* This is the entry point method.
*/
public void onModuleLoad() {
// Instanciation du module racine MVP4G.
Mvp4gModule module = (Mvp4gModule) GWT.create(Mvp4gModule.class);
module.createAndStartModule();
// La vue du module principal est positionné dans la page.
RootLayoutPanel.get().add((Widget)module.getStartView());
}
}

Cette classe ne bougera plus à l'avenir. Avant de lancer l'application et constater les changements, il conviendra de nettoyer le fichier HTML en supprimant le contenu qui n'est plus utile, c'est à dire tout depuis le tag H1 jusqu'à BODY.

Bazinga!


Nous venons de migrer une simple application GWT vers un socle technique lui permettant d'accueillir à l'avenir une grande quantité de services RPC ainsi qu'une interface riche, complexe et souple.

mardi 8 février 2011

Il faut lire!

Travailler sur des projets informatique où des dizaines de fichiers sources sont manipulés sur de longue durée et par de nombreuse personnes n'est plus envisageable sans un outil permettant d'assurer un historique des modifications.

Dans la galaxie d'outils permettant d'assurer la gestion des fichiers sources brille une étoile un peu particulière: Git. Après avoir utilisé Clearcase (de Rational) nécessitant un lourd workflow dans la gestion des sources, puis Subversion offrant une liberté de mouvement totale quant à la façon de traiter les fichiers sources, voici celui qui permet le mieux a mon sens la liberté absolue sans contraintes de connectivité.

J'en ferai le détail dans d'autres ticket à venir mais en attendant voilà un bouquin qu'il est bien pour lire.

dimanche 28 novembre 2010

Lire et écrire sur du NTFS avec Snow Leopard

Mes premiers pas dans le monde Apple ont commencés avec Léopard, i.e. Mac OS X 10.5. Cette migration s'est passée sans encombre dans la mesure où j'utilisais régulièrement Linux et quitter un environnement Unix pour un autre environnement Unix n'est pas une révolution en soi.

Mais des problèmes se sont posés lors de l'utilisation de mon disque dur externe. On a beau avoir aujourd'hui le choix dans le système d'exploitation, on ne peux échapper à l'utilisation de Windows, c'est pourquoi j'ai décidé, à la reception du dit disque dur, de le formater en NTFS ce qui avait les avantages suivant:
  1. Stocker des fichiers dont la taille excède 4 Go, ce qui est impossible sur une partition FAT,
  2. Être utilisable naturellement sur Windows (même celui des gens qui sont encore dans la matrice ;) ET sous Linux (FUSE permettant un accès en Lecture / Écriture depuis Ubuntu)
Donc lors de mon passage sur Léopard, la seule solution fut d'utiliser MacFUSE, un portage sous Mac de FUSE que j'utilisais déjà avec Ubuntu. L'installation s'est bien passée mais l'utilisation au quotidien se révélait quelque peu difficile:
  1. J'ai vite compris de ne JAMAIS mettre MacOS en veille sous peine de perdre le système de fichier, récupérable avec un Windows et la commande chkdsk,
  2. La copie de fichier volumineux prend à la fois beaucoup de CPU et beaucoup de temps.
Voilà maintenant près d'un an que je suis passé sur Snow Leopard et je viens juste de découvrir ce ticket permettant d'activer le support natif du NTFS. En effet, Snow Leopard est une avancée majeure du système à la pomme vers le monde Microsoft puisqu'il supporte, entre autre, naturellement l'intégration avec Exchange, et donc aussi du NTFS.

Mes premières utilisation du support natif NTFS montrent une bien meilleure consommation CPU (très faible) et un taux de transfert raisonnable (quelques minutes pour le transfert de près de 4Go)

mardi 16 novembre 2010

Installer Java 5 proprement sur un Ubuntu

En développant une librairie pour Android, je me suis aperçu que j'utiliser bien malgré moi une méthode introduite dans Java 6 (String#getBytes(Charset)).

Content de moi je fus lorsque j'avais réalisé mes tests unitaires, qu'ils étaient enfin passés au vert et tout cela avec mon IDE favori Eclipse configuré comme il se doit via Maven.
En effet, j'ai pris l'habitude d'inclure dans le pom de mes projets, une configuration du plugin maven-eclipse-plugin référençant un Runtime Environment en lieu et place de la JDK du workspace, cela permettant à Eclipse de choisir la bonne JDK en fonction de la specification souhaitée (j2sdk 1.4, jdk 1.5, JDK 6).

Encore fallait-il qu'il y ait une JDK 1.5 installée sur mon poste afin que la boucle soit bouclée. Et là, tout bascule: la JDK 1.5 n'est plus fournie de manière standard par les dépôts depuis la version 9.04 d'Ubuntu.

Ce poste m'a sauvée la mise en m'indiquant la marche à suivre pour installée proprement cette version 1.5 de la JDK qui n'est plus supportée par Ubuntu mais d'actualité sur la plateforme mobile Android.

vendredi 16 avril 2010

rEFIt passe en version 0.14


Tout est dit!

rEFIt qui est le petit programme qui permet aux utilisateurs de Mac d'installer et d'utiliser un O.S. alternatif à MacOS X. Ce dernier est donc passé en version 0.14.

Parmis les améliorations, la plus visible on notera la présence d'une icone propre aux distributions GNU Linux et non plus ce vieux logo d'un certaine fenêtre ... et biensûr des correctifs. Pour plus d'info, rendez-vous sur cette page.