mercredi 27 mai 2009

Traitement asynchrone avec une api REST

Une des critiques faite sur REST est le fait de son attachement au protocole HTTP. Ce protocole est "déconnecté" : il est impossible de reprendre une connexion précédente. Cela pose un problème dans le cadre de longs traitements asynchrones et de notifications.

Une possibilité pour palier à cette faiblesse est de considérer elle aussi cette transaction comme une ressource. Cette ressource est retournée immédiatement au client suite au premier appel. Ensuite, le client pourra récupérer l'état de cette transaction. Par exemple, supposons que nous souhaitons lancer un batch "batch1" de manière asynchrone :

GET /batchs/batch1 HTTP/1.1
Host: xyz.com
Content-Type: application/xml; charset=utf-8


HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
Location: /transactions/1234
http://xyz.com/transactions/1234


La réponse est immédiatement renvoyée et contient l'URI de la transaction utilisée. Le client peut consulter cette ressource pour connaitre le status de son traitement en cours.

GET /transactions/1234 HTTP/1.1
Host: xyz.com
Content-Type: application/xml; charset=utf-8


HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
Content-Length: nnn

batch1
In Progress

vendredi 6 février 2009

Internationalisation (i18n) en Javascript


Difficile de gérer le multilinguisme (i18n) en Javascript ! Et pourtant, on est souvent tenté de rapidement écrire dans son code des messages destinés à l'utilisateur. La gestion a posteriori de plusieurs langues devient alors un casse tête. Mieux vaut anticipé et externaliser ses libellés.
var labels_fr =  {
'hello' : 'Bonjour {0}, tu as {1} ans',
'quit' : 'Quitter ?'
}
var labels_en =  {
'hello' : 'Hello {0}, you are {1}',
'quit' : 'Quit ?'
}

La classe ci-dessous permet d'obtenir simplement le libellé souhaité en fonction de la langue :
var i18n = new I18n();
i18n.load(labels_fr);
i18n.translate('hello','JC', '33');

Plus propre, non ?
La classe en question (UPDATE 18/10/2010 : prise en compte de plusieurs patterns) :
/**
* I18n est une classe permettant d'obtenir un libellé
* caractérisé par une clé dans une map contenant un
* ensemble de libellés traduits dans la langue choisie
* @constructor
*/
var I18n = function(){}

/**
* Charge la map de clés/libellés
* @param {Object}  translations Les clés/libellés traduits
* @constructor
*/
I18n.prototype.load = function(translations) {
this._translations = translations;
}

/**
* Remplace les nombres entre accolades ({0}) par les
* chaînes de caractères passées en paramètre
* exemple  : _format('Hello {0} ! ', 'guy') => Hello guy !
* @param {String}  format La chaîne de caractère à formater
*/
I18n.prototype._format = function(format) {
var args = arguments[1];
return format.replace(/\{(\d+)\}/g, function(m, i){
return args[i];
});
}

/**
*  Retrouve le libellé correspondant à la clé passée en paramètre
* et le formate en fonction des paramètres supplémentaires
* exemple  : translate('hello', 'guy') => Hello guy !
* @param {String}  key La clé à traduire
*/
I18n.prototype.translate = function(key) {
if (typeof(this._translations)!='undefined' && this._translations[key]) {
return this._format(this._translations[key],Array.prototype.slice.call(arguments,1));
}
return key;
}

jeudi 2 octobre 2008

JSF (MyFaces) et TimeZone

Les spécifications JSF font que le fuseau horaire (timezone) par défaut d'une conversion Date -> String est le GMT.
Celui du serveur n'est pas pris en compte.
Pour MyFaces, ces spécifications sont respectées depuis les versions supérieures à 1.1.0

package javax.faces.convert;
...
public class DateTimeConverter implements Converter, StateHolder {
...
private static final TimeZone TIMEZONE_DEFAULT = TimeZone.getTimeZone("GMT");
...
}

Ainsi, il faudrait préciser le fuseau horaire dans toutes les pages affichant des dates si on souhaite en utiliser un autre.

<h:outputText value="#{bean.date}">
<f:convertDateTime timeZone="Europe/Paris"/>
</h:outputText

Fastidieux!
Le plus simple est d'écrire son propre DateTimeConverter :


import java.util.TimeZone;
import javax.faces.convert.DateTimeConverter;
public class MyDateTimeConverter extends DateTimeConverter {
public MyDateTimeConverter() {
super();
setTimeZone(TimeZone.getDefault());
}
}

et de faire en sorte qu'il devienne le converter par défaut :

<converter>
<converter-for-class>java.util.Date</converter-for-class>
<converter-class>MyDateTimeConverter</converter-class>
</converter>

mardi 3 juin 2008

Utilisation avancée du plugin Maven Hibernate 3


Le plugin Maven Hibernate 3 permet d'intégrer facilement Hibernate 3 au sein d'un projet géré par maven. Ce plugin est régulièrement maintenu et sa communauté réactive mais on peu cependant déplorer une documentation succincte.
On y trouve en effet des exemples que pour des cas simples d'utilisation.
Par exemple, on y trouve comment générer un schéma pour une base de donnée avec un seul fichier de configuration mais comment faire pour générer des schémas pour plusieurs bases de données, chacune possédant son propre fichier de configuration .hbm.
Pour cela, il faut appeler plusieurs fois hbm2ddl :

org.codehaus.mojo
hibernate3-maven-plugin
2.2-SNAPSHOT


generate-database-1
process-test-resources

hbm2ddl



target/classes/hibernate1.cfg.xml
target/classes/jdbc1.properties




generate-database-2
process-test-resources

hbm2ddl



target/classes/hibernate2.cfg.xml
target/classes/jdbc2.properties






${jdbc1.groupId}
${jdbc1.artifactId}
${jdbc1.version}


${jdbc2.groupId}
${jdbc2.artifactId}
${jdbc2.version}




Cette logique reste vraie avec les autres "goals".

jeudi 27 mars 2008

Problème de validation XML avec JAXP

L'api JAXP permet l'analyse et la transformation de documents XML. Une implémentation de JAXP est disponible dans JAVASE 5.0 mais il est aussi possible d'utiliser l'implémentation fournie par Apache Xerces.
Pour analyser le fichier data.xml avec un parser SAX :

SAXParserFactory spfactory = SAXParserFactory.newInstance();
spfactory.setNamespaceAware(true);
SAXParser saxparser = spfactory.newSAXParser();
saxparser.parse(new File("data.xml"));

Et avec un parser DOM :

DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
dbfactory.setNamespaceAware(true);
DocumentBuilder domparser = dbfactory.newDocumentBuilder();
domparser.parse(new File("data.xml"));


La fonctionnalité qui nous intéresse ici est la validation d'un document par rapport à un schéma.

DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
dbfactory.setNamespaceAware(true);
dbfactory.setXIncludeAware(true);

DocumentBuilder parser = dbfactory.newDocumentBuilder();
Document doc = parser.parse(new File("data.xml"));

DOMSource xmlsource = new DOMSource(doc);

SchemaFactory wxsfactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

//load a W3C XML Schema
Schema schema = wxsfactory.newSchema(new File("myschema.xsd"));

// create a validator from the loaded schema
Validator validator = schema.newValidator();

//validate the XML instance
validator.validate(xmlsource);

Si cet exemple fonctionne avec l'implémentation fournie avec JAVASE 5.0, une exception est toujours levée avec Xerces :

Exception in thread "main" org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'root'.

En effet, l'implémentation Xerces de Validator#validate ne semble pas supporter un objet de classe DOMSource en paramètre.
Pour contourner ce problème, il faut utiliser la fonctionnalité de transformation de JAXP et transformer notre document DOM(DOMSource) en Stream(StreamSource).

DOMSource xmlsource = new DOMSource(doc);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(xmlsource , result);
validator.validate(new StreamSource(new StringReader(result.toString())));


Pour aller plus loin : http://issues.apache.org/jira/browse/XERCESJ-1163

mardi 5 février 2008

Sticky sessions

Dans un environnement multi-serveurs (cluster), le mécanisme de sticky session (affinité de session) assure qu'un client qui a ouvert une session sur un serveur sera toujours aiguillé vers ce même serveur durant sa session.
Par opposition, la réplication de session permet de garder sa session quelque soit le serveur sur lequel l'utilisateur est aiguillé.

jeudi 17 janvier 2008

Oracle rachète BEA

Ca se tramait depuis un certain temps mais c'est maintenant officiel :

Oracle to Acquire BEA Systems

L'un des 2 serveurs d'application OC4J ou Weblogic Server devrait passer à la trappe.

De même, on peut se demander comment va se positionner la nouvelle entité par rapport à Workshop concurent direct de JDevelopper.

En tout cas, ce rachat sucite de nombreuses réactions dont celles de Rod Johnson et Gigaspace qui nous encouragent à s'affranchir de l'architecture j2ee dont les 2 seuls concurrents directs sont maintenant Oracle et IBM.