darkBlog

jeudi 30 mars 2006

Décodage des URL avec Domino : oui mais non

Décidemment, je crois que je ne m'en sortirai jamais avec ces histoires d'encodage et décodage d'URL. Pour ceux qui prennent le sujet en cours de route, petit rappel chronologique :

Quel est le problème cette fois-ci, me demanderiez-vous ? Et bien observez l'extrait de code publié dans ce billet, rapprochez le de cet autre récent billet évoquant la limite de taille des chaînes de caractères dans une formule, et je vous laisse deviner la suite : un texte à décoder trop volumineux amène à un lamentable croûtage de l'Evaluate(). Voilà qui est bien fâcheux. Alors, comment gérer ce type de situation, sachant qu'il n'existe pas de méthode native en LS pour décoder des URLs et que l'utilisation des formules est à proscrire ?

Pour peu que l'on recherche sur le net, on peut trouver des fonctions LS de décodage des URL développées par des tiers : chez Mike Golding, ou encore chez Johan Känngård par exemple. Malheureusement, lors des différents tests que j'ai pû mener, les caractères spéciaux étaient mal restitués, pour des raisons que je n'ai pas vraiment réussi à élucider mais manifestement liées au fait que la tâche HTTP de Domino sert et reçoit (dans ma configuration) les pages en UTF-8 et que, à en croire la documentation, le jeu de caractères natif de l'environnement Domino est celui de la plateforme sur laquelle il s'exécute (dans mon cas Windows 2000), soit selon toutes vraissemblances l'ISO-8859-1 (en tout cas, pas l'UTF-8). Quoi qu'il en soit, même si ce point reste obscur (et ma déduction probablement bancale), il semble clair que le problème est lié à l'encodage des caractères, et on peut en conclure sans crainte qu'il manque un paramètre à toutes ces fonctions de décodage des URL : le jeu de caractères considéré.

Les recherches du coté de LotusScript n'ayant pas donné grand chose de concluant, tournons nous vers Java. Premier réflexe : jeter un coup d'oeil aux spécifications de l'API. Chouette, se dit-on après quelques recherches, Java possède une classe de décodage d'URL : java.net.URLDecoder, et même qu'on peut spécifier le jeu de caractères !
La joie est toutefois de courte durée, puisqu'on s'aperçoit (plus ou moins) rapidement que ce paramètre a été introduit dans la version 1.4 de Java et que la version précédente en est démunie. Or, avec Domino 6.x, c'est bien une JVM 1.3 qui est embarquée. Aussi, à moins d'utiliser un serveur Domino 7 - ce qui n'est pas mon cas - on est de nouveau coincé.
C'est finalement chez Apache que nous trouverons notre salut, du coté du projet Jakarta Commons, avec l'ensemble de composants Commons Codec et plus précisément la classe URLCodec qui - miracle - permet de spécifier un jeu de caractères.

Pour utiliser cette librairie avec Domino, deux solutions : soit dans un agent Java, soit dans un agent LotusScript via LS2J. J'ai opté pour la seconde solution car mon agent LS devant réaliser le décodage était déjà écrit et utilisait tout un tas d'autres bibliothèques LS que je n'avais pas forcément envie de porter en Java. Mais rien ne vous empêche choisir la première.
Pour une solution LS2J, la première étape consiste à mettre au point une librairie de scripts interfaçant cette librairie, que l'on va s'empresser de créer en l'intitulant "URLDecoder" et en y incluant le fichier commons-codec-1.3.jar (téléchargeable ici).

Inclusion de la librairie Commons Codec dans la librairie de scripts URLDecoder
Inclusion de la librairie Commons Codec dans la librairie de scripts URLDecoder

Puis on y insère le code suivant :

import org.apache.commons.codec.net.URLCodec;

public class URLDecoder {
  public static String decode(String s) {
    try {
      URLCodec codec = new URLCodec("UTF-8") ; // selon la plateforme
      return codec.decode(s) ;
    }
    catch (org.apache.commons.codec.DecoderException e) {
      return "URLDecoder error : "+e.getMessage();
    }
  }
}

L'interface Java étant créée, on peut maintenant l'utiliser très naturellement dans nos agents LotusScript via des appels LS2J :

Uselsx "*javacon"

Dim jSession As JavaSession
Dim urlDecoder As JavaClass
Dim decodedString as String

Set jSession = New JavaSession()
Set urlDecoder = jSession.GetClass("URLDecoder")

decodedString = urlDecoder.decode("la chaîne encodée")

Et voilà. Avec cette méthode, les chaînes de caractères sont proprement décodées, les caractères spéciaux correctement restitués, et ce sans limite de taille (puisque c'était là notre problème, pour rappel). Est-ce à utiliser systématiquement pour autant ? Non, car les temps d'exécution sont nettement plus longs qu'avec une méthode basée sur les formules. Toutefois, quand vous ne maîtrisez pas la longueur de la chaîne que vous devez décoder, cette solution peut vous sauver la mise.

Et j'espère ne plus jamais avoir à revenir sur ce sujet.

vendredi 24 mars 2006

Web dynamique avec Domino : pareil, mais en mieux

Vous vous souvenez de la méthode de développement web dynamique que j'ai présenté fin 2004 ? L'idée de base était d'utiliser des sous-masques comme support de cache, en regénérant des bouts de code HTML (typiquement des "portlets") sur action de l'utilisateur ou sur exécution d'une tâche planifiée, en les stockant dans des sous-masques (donc directement dans le design de la base) puis en les restituant aux visiteurs en incluant lesdits sous-masques. Ce qui permet, par exemple, de monter une architecture type portail sans frame ni javascript, et surtout sans la moindre consommation de ressource serveur (pas d'agent WQO, pas de @DbLookup, etc), ce qui est loin d'être négligeable quand la performance est un point critique. Inconvénient toutefois, de par la nature du mécanisme (un cache), la gestion de profils est rendue difficile.

Quoi qu'il en soit, Je n'en ai jamais vraiment reparlé depuis, mais sachez que j'ai mis en place l'année dernière (une partie d')un intranet basé sur ce principe, et que cela fonctionne furieusement bien : 20 000 pages vues par jour, des temps de réponse très faibles, et absolument aucun problème malgré l'apparent bidouillage qu'il y a derrière. Pour éviter tout conflit sur la structure, lors des opérations critiques j'ai utilisé des Locks à la manière d'un mutex. Et pour éviter l'explosion de la base en cas de pépin (ce qui n'est soit dit en passant jamais arrivé), j'ai déporté les sous-masques modifiés par programmation dans une base à part ; la base principale contenant les documents n'est donc jamais modifiée par cette voie. Une sécurité supplémentaire qui induit cependant une maintenance plus délicate, car elle nécessite de fait l'inclusion de sous-masques entre bases (j'ai d'ailleurs découvert des choses amusantes à ce sujet, j'y reviendrai une autre fois).

Si je reviens aujourd'hui sur le sujet, c'est parce que je me suis rendu compte lors de ma récente exploration de DXL que celui-ci introduit des méthodes natives pour réaliser ce même traitement plus simplement, et surtout plus proprement. Explications. Un export DXL d'un sous-masque ressemble à ceci (j'ai simplifié le document XML pour des raisons de lisibilité) :

<?xml version="1.0" encoding="utf-16"?>
<!DOCTYPE subform>
<subform name='portlet-de-test' xmlns='http://www.lotus.com/dxl' replicaid='C12A00Z2013C1485'>
<noteinfo unid='4C240A1EA7321952C131713410135EDC' noteid='5ef'/>
<body><richtext><par><run html='true'>ceci est du code HTML de test&lt;br>et une seconde ligne</run></par></richtext></body>

</subform>

Vous constaterez donc que le code HTML de notre sous-masque est contenu dans un élément par (paragraphe), lui même contenu dans un élément richtext, et que le tout est HTML relai-isé (passthru HTML) via un élément run. Plutôt intéressant, non ? De là à imaginer un traitement qui réalise un export XML d'un sous-masque, qui en modifie les parties qui vont bien (en l'occurence notre code HTML) à l'aide d'un parser XML (DOM ou SAX) voire de XSLT, puis qui réimporte le tout dans la base en remplaçant l'existant, il n'y a qu'un pas. Pas que nous allons franchir un peu plus bas.

Contrairement à leurs homologues Java, les processeurs XML disponibles en LotusScript supportent la notion de pipelining (chaînage) : la sortie d'un processeur sera l'entrée du suivant. Du coup, la réaction en chaîne se produit automatiquement et il n'est pas nécessaire de passer par des fichiers et/ou variables temporaires. Au final, le tout tient en quelques lignes de code (ici avec un parser DOM) :

' Initialisations des processeurs XML
Set Me.exporter = session.CreateDXLExporter()
Set Me.parser = session.CreateDOMParser()
Set Me.importer = session.CreateDXLImporter()

' Exporter
exporter.OutputDOCTYPE = True
Call exporter.setInput(subform)

' Parser
On Event PostDOMParse From parser Call doPostDOMParse
Call parser.setInput(exporter)

' Importer
importer.DesignImportOption = DXLIMPORTOPTION_REPLACE_ELSE_CREATE
Call importer.setInput(parser)
Call importer.setOutput(db)

' Lancement du traitement en chaîne (pipeline)
Call exporter.Process()

Ici subform est notre sous-masque ; il est manipulé comme un document notes et est récupéré via une vue modifiée pour lister les sous-masques de la structure (voir ici pour plus d'infos). L'option DXLIMPORTOPTION_REPLACE_ELSE_CREATE indique au DXL Importer de remplacer l'élément de structure s'il existe déjà (et c'est justement le cas, tiens). La transformation proprement dite du document XML, que je ne détaillerai pas, est réalisée dans le callback doPostDOMParse qui effectue un parcours du document XML à l'aide de DOM et modifie les noeuds concernés. D'ailleurs, la création d'un noeud de type texte (CreateTextNode()) entraîne automatiquement la transformation des caractères spéciaux en entités HTML. C'est beau.

On arrive donc au même résultat qu'avec la méthode présentée fin 2004, mais avec l'assurance d'un code robuste et entièrement basé sur des fonctionnalités natives de Domino, ce qui est un peu plus rassurant quand on envisage de le déployer chez un client. Quant à DXL, je dois avouer que c'est l'une des choses les plus excitantes que j'ai découvert avec Domino dernièrement. D'autres billets à venir sur le sujet. Enfin, ceux qui le souhaitent pourront trouver la classe ici : CacheManagement.lss.

lundi 20 mars 2006

Versioning de bases Domino avec Subversion

Il y a une idée qui me trottait dans la tête depuis pas mal de temps et que j'ai évoqué il y a quelques jours : versionner les bases Domino avec Subversion. Enfin, pas les bases en elles-même car ça n'aurait pas vraiment d'intérêt, mais plutôt leurs exports DXL (DXL permettant d'exporter une base sous forme de fichier XML structuré selon la DTD Domino).

Dès lors, on pourrait imaginer historiser chaque évolution d'une base dans un repository SVN (ce qui peut se faire sans recourir à DXL), mais surtout comparer différentes versions de cette même base (c'est à partir de là qu'intervient DXL), voire même assembler les travaux de plusieurs développeurs sur une même ressource (un agent, une librairie de script..) ou pourquoi pas générer des nighty builds (sachant qu'il est possible de concevoir une base depuis des données DXL, soit l'opération inverse de la précédente)... Plutôt alléchant, non ?

Différences entre 2 versions d'une même base avec TortoiseMerge
Différences entre 2 versions d'une même base avec TortoiseMerge

En pratique, le bilan est plutôt mitigé. Si la comparaison donne de bons résultats sur une base ayant subit peu de changements (voir la capture d'écran ci-dessus), elle a tendance à s'emmêler les pinceaux sur une base dont la structure a pas mal évolué, et ne retrouve pas forcément ses petits (du coup, un bout de code qui n'a pas changé peut être considéré comme supprimé dans l'ancienne version et ajouté dans la nouvelle).

Pour éviter cet effet, il conviendrait de réduire la granularité ; non pas historiser des exports de bases complètes, mais des exports d'ensembles d'éléments de design, voire même des éléments de design un par un. Ce qui signifie exporter les éléments de design un par un. Bien que cela reste techniquement faisable moyennant l'agent qui va bien, cela pose derrière, je crains, des problèmes de cohésion : comment vérifier l'évolution d'une base dans son ensemble, sachant qu'il n'y a plus d'historique global de la base ?

Bref, ce n'est pas si évident que ça. J'avoue ne pas trop savoir quoi en penser ; je suis convaincu qu'il y a quelque chose de puissant à faire avec Subversion et Domino, mais en l'état cela ne me paraît pas vraiment exploitable. Vous en pensez quoi ?

Pour info, j'ai essayé d'interfacer un agent Java avec un repository SVN pour automatiser le commit de la base. La seule API que j'ai trouvé est JavaSVN, qui malheureusement nécessite J2SE 1.4 et supérieur. Or, c'est le JDK 1.3 qui est embarqué dans Notes et Domino R6.x. N'ayant pas plus de serveur Domino que de client ou designer Lotus R7, je n'ai pu pousser mes tests plus loin.

mardi 14 mars 2006

Limite de taille d'une chaîne de caractères dans une formule

Julien a évoqué dernièrement les limitations connues de Domino 5, 6 et 7. Pour ma part j'ai été confronté aujourd'hui à ce qui semble être une limite peu voire pas connue de Domino (R6.5) : les chaînes de caractères dans les formules ont une taille qui semble se limiter à environ 2050 caractères. Au delà, il devient impossible de sauvegarder la formule, et le message d'erreur Quoted string is too long est retourné. Je n'ai pas trouvé la limite exacte, j'imagine qu'elle s'exprime en octets et non en nombre de caractères, et qu'en ce sens elle doit varier selon la nature desdits caractères (codés sur 1 ou 2 octets en interne ?).

Quoted string is too long : Lorem Lipsum...

Dans l'exemple ci-dessus on se rend compte aisément du problème, mais pour peu que le traitement soit réalisé au travers d'un Evaluate() embarqué dans du code LS ou Java, la formule plante sans rien dire, et le script sans raison apparente.
C'est ça qui est beau avec Domino : quand on entame avec morosité une journée de développement que l'on croit déjà toute traçée, il y aura toujours une petite subtilité sur laquelle bûter pour egayer la journée !