Christophe Nowicki

February 10, 2009

Système d’impression PDF “RESTful” pour le Web

Le monde du web et celui de l’impression, ont décidément du mal à se rencontrer.
La prise en charge de l’impression est bien souvent très problématique dans le cas d’un projet web.

Dans la plupart des cas, il est possible de s’en sortir en utilisant les possiblités offertes par les feuilles de style CSS.
Mais le rendu final du document n’est pas garanti, les différents navigateurs interprètent la feuille de style selon leur humeur
et cela ne fonctionne pas dans le cas de documents complexes.

Dans ce cas, l’unique solution est de produire un document au format PDF, unique garantie pour une impression de qualité.
Je vais donc décrire dans cet article, la solution que nous avons mis en place dans le cadre du projet MAVISE.

Historique

La base de données MAVISE fourni les données sur l’ensemble des chaînes de télévision accessibles dans l’Union européenne.

Elle a été développé par la société Easter-eggs en collaboration avec l’Observatoire européen de l’audiovisuel (OEA) pour le compte de la DG Communication de la Commission Européenne.

Dans la 1ère phase du projet nous avons développé un système d’impression avec des feuilles de style.
Ce système ne donnait pas entièrement satisfaction au client.

En effet, les pages du projet étant très complexes, le résultat de l’impression produite par les différents navigateurs était très aléatoire (coupure en plein milieu d’un tableau, impression sur plusieurs pages et saut de pages inexpliqués).

Le résultat n’était pas professionnel et il était difficile pour le client d’alimenter un rapport avec les contenu de la base.
Nous avons donc proposé de mettre en place un export PDF pour l’ensemble des éléments de l’application.

Choix techniques

Au fils du temps, je suis devenu un expert de la production de document PDF,
en effet, j’ai été en charge de l’export PDF dans de nombreux projets.

J’ai eu le plaisir d’utiliser plusieurs API pour produire des documents au format PDF, comme par exemple :

Le travail pour produire des documents PDF consiste dans la plupart des cas à dessiner avec l’aide de ces diffèrentes
API, le contenu du document. (texte, tableaux, graphique et mise en page).

Il s’agit d’un travail long et fastidieux, qui consiste à fournir une suite d’instructions pour former le document.
Cette technique montre vite ses limites :

  • les demandes de modification de la mise en page du document produit, sont lourdes et nécessitent beaucoup de temps ;
  • le code pour produire le document est difficilement rationalisable ;
  • elle ne convient pas à des documents de grande taille.

Dans le modèle MVC, qu’utilise la plupart des applications Web, nous utilisons un système de template pour l’affichage (la vue).

Alors pourquoi bon “coder” la présentation des documents PDF?

Pour cette raison, j’ai proposé au client de bâtir le système d’impression des documents PDF sur un système de template.

Ce système repose sur le langage de programmation XSL-FO, les projets Apache FOP et Apache Cocoon.

Architecture du système d’impression

  1. Lorsque l’utilisateur clique sur le lien PDF, il est redirigé vers Apache Cocoon ;
  2. Cocoon, récupère l’ensemble des informations directement sur le site web, via l’API REST ;
  3. Il agrége les informations dans un document XML ;
  4. Ce document est transformé en FO à l’aide d’une feuille de style XSLT ;
  5. Le document est produit par Apache FOP ;
  6. L’utilisateur obtient le document final.

L’interface entre le Site Web et Apache Cocoon

L’architecture du système d’impression repose sur le fait que l’ensemble des variables manipulées par notre application sont accessibles à l’aide d’URL REST.

En effet, il est possible d’afficher le contenu des diffèrentes variables manipulées par l’application à l’aide d’adresses URL spécifiques.
Voici un exemple de sortie :

<?xml version="1.0" encoding="UTF-8"?>
<array>
<is_admin />
<country>
<id>1</id>
<iso3166>FR</iso3166>
<name>France</name>
<enabled>t</enabled>
<minimal_age_of_audience>4+</minimal_age_of_audience>
</country></array>
...

Il est donc possible de récupèrer l’ensemble des informations du site à l’aide du protocol REST.

Nous avons aussi de très nombreux tableaux dans l’application, pour les récupérer, nous utilisons le format XML natif du contrôleur de tableaux dhtmlxGrid.
Voici un exemple de sortie : fichier XML pour dhtmlxGrid.

Agrégation des données dans un seul fichier

Pour récupérer l’ensemble des données du site dans un fichier XML, j’utilise la directive include, offerte par Cocoon :

$ cat /var/lib/tomcat5/webapps/cocoon/mavise/program/program.xml
<?xml version="1.0"?>

<page name='program' xmlns:i="http://apache.org/cocoon/include/1.0">
<i:include src='cocoon:/webui_program'/>
...

Cette directive dit à Cocoon, d’inclure le contenu qui se trouve à l’url cocoon:/webui_program, défini dans le fichier
sitemap.xmap :

<!-- Webui -->
<map:match pattern="webui_*/*">
<map:generate src='http://localhost/{1}?id={2}&presenter=rest&filter=webui'/>
<map:serialize type="exml"/>
</map:match>

Transformation XSLT

Pour transformer les données contenues dans ce fichier, nous avons créé notre propre feuille de style XSLT, pour produire un document FO.
Cette feuille de style prend en compte la mise en forme des diffèrentes données du site.

Grâce à ce système, l’ensemble de la mise en forme est centralisé dans un seul fichier.
Il est aussi possible d’ajouter du contenu statique dans le PDF à l’aide de balises dédiées.
Comme par exemple, une balise copyright, qui sera transformées en un texte statique dans tous les documents PDF.

Le pipeline pour les documents PDF

Voici le pipeline final pour produire des documents PDF :

<!-- PDF -->
<map:match pattern="*/*/*.pdf">
<map:generate src='{1}/{3}.xml'/>
<map:transform src="mk_id.xsl" type="xslt">
<map:parameter name="val" value="{2}" />
</map:transform>
<map:transform type="include">
<map:parameter name="parallel" value="true"/>
<map:parameter name="support-caching" value="true"/>
<map:parameter name="expires" value="600"/>
</map:transform>

Le point intéressant à noter dans ce pipeline est l’encodage des arguments passés via l’URL pour le document PDF.
En effet, les URL pour produire les PDF sont formés de la manière suivante :

http://mavise.obs.coe.int/cocoon/mavise/channel/2157/channel.pdf

L’URL contient, le nom du module et l’identifiant de la chaîne.
De cette manière, il est possible de passer d’autres arguments au système d’impression (comme par exemple, changer l’orientation du document).

Performances

Au niveau des performances du système d’impression, nous n’avons pas une charge très importante sur la génération des documents PDF.
La production des documents est une tâche complexe et nécessite donc de manière génèrale beaucoup de ressources.
Néanmoins, il y a une chose intéressante à noter au niveau de la génération des documents PDF.
Celle-ci est plus rapide que l’affichage dans un navigateur.
En effet cela est lié au fait que les échanges de données se font en local sur le serveur, il y a donc moins de bande passante utilisée que dans le cas de l’affichage de cette même page par le navigateur.

Les petits désagréments du format PDF

Nous avons rencontré un petit problème, avec la gestion des polices UTF-8, en effet, comme la base contient des données sur des chaînes de télévision et des entreprises Turques, nous avons eu besoin d’embarquer une police UTF-8 à l’intérieur du document.
Et visiblement cela pose des problèmes à certains lecteurs.

Quelques liens utiles

Voici quelques liens utiles qui nous ont aidé dans la réalisation de ce système d’impression :

Conclusion

En conclusion, j’aimerais remercié l’Observatoire européen de l’audiovisuel (OEA), pour nous avoir fait confiance pour la mise en place de cette solution.
Au 1er abord, le schéma de l’architecture n’est pas très engageant ;-)

J’espère que cette solution donnera des idées à d’autres personnes, pour leur système d’impression.
L’objectif final est de faire des économies de papier par rapport à une impression faite via Internet Explorer ;-)

Filed under: Work — Tags:, , , , , , — cscm @ 22:43

February 4, 2009

Retour sur l’optimisation des performances du projet MAVISE

La base de données MAVISE fournir les données de base sur l’ensemble des chaînes de télévision accessibles dans l’Union européenne.
Elle à été développer par la société Easter-eggs pour le compte de la DG Communication de la Commission européenne.
Je vais décrire dans cet article les diffèrentes méthodes que nous avons mise en place pour optimiser les performances de cette application.

Problèmatique

La base de données contiens beaucoups d’informations :

  • la description du paysage audiovisuele de 30 pays ;
  • des fiches sur 5000 chaînes de télévision ;
  • des fiches sur 4000 entreprises ;
  • des fiches sur 8000 programmes de télévision ;

L’ensemble des ses données ne sont pas accessibles au grand publique.

D’un point de vu technique, la base de données PostgreSQL contient :

  • 115 tables ;
  • 100 vues ;
  • 220Mo de données.

Cela vous donne une petite idées de la taille de l’application.
La complexitées de l’application, la quantité de données disponibles et leur affichage nous as posées de nombreux problèmes :

  • Quantité de données très importante à transferer entre le client et le serveur Web ;
  • Requêtes complexes au niveau de la base de données ;

Optimisation de la base de données

Pour l’optimisation de la base de données, il faut faire attention aux points suivants :

  1. L’ORM (DB_DataObject dans notre cas) ne doit faire aucune opération de jointure . Il utilise toujours une vue dédier pour chaque fonctionnalitées ;
  2. Chaque vue ne calcule que les champs qui sont nécessaire à l’affichage ;
  3. Le plan d’execution de chaque vue est optimiser à l’aide d’index. Pour trouver quels sont les indexes à créer, il faut utiliser l’instruction EXPLAIN ANALYSE , comme décrit sur le wiki de Postgres : Using EXPLAIN.

Le travail principal consiste donc à éxécuter toutes les vues de l’application pour optimiser les plans d’éxécution à l’aide d’indexes.
Pour mieux comprendre la sortie de l’instruction EXPLAIN ANALYSE, j’utilise l’outil suivant : explain-analyze.info.

Pour savoir quelles sont les optimisations les plus pertinantes, il est intéressant d’analyser les logs du serveur Postgres à l’aide du logiciel Practical Query Analysis.

Il faut aussi “tuner” la configuration du serveur de base de données pour utiliser le maximum de la mémoire disponible sur le serveur.

Javascript

Nous avons utilisé le framework Javascript Mootools, le contrôleur de tableau dhtmlxGrid et l’éditeur de contenu TinyMCE.

Dans chaque page de l’application, il faut faire attention à ne charger que les fichiers nécessaire.
En effet, ces outils, bien que très pratiques sont très volumineux et augementent la taille des pages dans des proportions importantes.

Dans certains cas, nous avons dépasser plus de 640Ko de code Javascript dans une page ;-)

Les solutions que nous avons mis en place pour réduire la taille de ces fichiers :

  • La compression Gzip, malheureusement nous avons rencontrer de problèmes très importants avec Internet Explorer ;
  • Nous avons ensuite opter pour YUI Compressor qui fonctionne bien mieux ;

Systèmes de Cache

L’application dispose de nombreux systèmes de caches :

  • Le système de cache du gestionaire de template Smarty ;
  • Notre propre système de cache pour le contenu des tableaux, réalisé à l’aide du module Pear Cache_Lite ;
  • L’API de XCache, pour certains calculs et XCache pour les opcodes PHP ;
  • Le système de cache d’Apache Cocoon pour le système d’impression ;
  • La cache du navigateur des utilisateurs. Il est possible de bien contrôler le contenu du navigateur des clients à l’aide du module mod_expires d’Apache.

Réduction de la taille des pages

Nous avons travailler pour réduire au maximum la taille des échanges entre le navigateur web et le serveur.
Pour cela nous avons utilisé la compression Gzip sur :

  • Le contenu des tableaux ;
  • Les pages produites par le système de tempate ;

Celle-ci est réalisé par le gestionnaire de tempon de PHP, à l’aide de la fonction ob_gzhandler ;

Outils utilisés

L’optimisation d’une application est toujours empirique, il est nécessaire d’avoir de bon outils pour observer les effets des modifications.
Pour cela nous avons utilisé :

Les liens utiles pour l’optimisation des performances d’une application Web

Pour finir, voici quelques liens très interessants sur l’optimisation d’une application Web :

Filed under: Programming,Work — Tags:, , — cscm @ 21:42

June 3, 2008

Validation d’un fichier CSV à l’aide d’un Schéma XML

Malgré tout le mal qu’il est possible de penser ou de dire sur le format de fichier CSV, celui-ci présente beaucoup d’avantages :

  • il est facilement manipulable pour les utilisateurs à l’aide d’un tableur comme Calc, Gnumeric ou bien Excel ;
  • il permet d’écrire rapidement et facilement des routines d’import / export ;
  • il permet d’échanger les données entre des bases et des systèmes hétérogène.

Mais ce format de fichier ne dispose pas d’un système de validation. Dans la plupart des cas ce sont les routines d’import / export qui prennent en charge la validation des données et de leur mise en forme.

Ce travail est pénible, répetitif et ennuyeux mais nécessaire si vous ne voulez pas qu’un fichier mal formaté ne corrompe les données de votre base.

L’objectif de cette article est de proposer une solution simple permettant la validation d’un fichier CSV en utilisant les fonctionnalités offertes par les schémas XML.

Les exemples fournis sont écrit à l’aide du language de programmation PHP5.

Transformation CSV vers XML

Votre fichier doit contenir sur la 1ère ligne, le nom de chaque cologne :

“CHANNEL”;”COMPANY”;”BVD_ID_Groupe_1″;”BVD_ID_Groupe_2″;”GENRE”;”URL”;”OTHER_URL”;”COMPETENT_AUTHORITY”
“Canal+ Film 1″;”C MORE ENTERTAINMENT AB”;”NL33268595″;”DE8330261794″;”CIN”;”http://www.canalplus.se”;”http://www.sbsbroadcasting.com/, http://www.prosiebensat1.de/”;”RTVV”
“Canal+ Film 2″;”C MORE ENTERTAINMENT AB”;”NL33268595″;”DE8330261794″;”CIN”;”http://www.canalplus.se”;”http://www.sbsbroadcasting.com/, http://www.prosiebensat1.de/”;”RTVV”
“Canal+ Film 2 Sport Weekend”;”C MORE ENTERTAINMENT AB”;”NL33268595″;”DE8330261794″;”CIN”;”http://www.canalplus.se”;”http://www.sbsbroadcasting.com/, http://www.prosiebensat1.de/”;”RTVV”
“Canal+ Film 3″;”C MORE ENTERTAINMENT AB”;”NL33268595″;”DE8330261794″;”CIN”;”http://www.canalplus.se”;”http://www.sbsbroadcasting.com/, http://www.prosiebensat1.de/”;”RTVV”
“Canal+ Film HD”;”C MORE ENTERTAINMENT AB”;”NL33268595″;”DE8330261794″;”CIN”;”http://www.canalplus.se”;”http://www.sbsbroadcasting.com/, http://www.prosiebensat1.de/”;”RTVV”

Pour transformer le fichier CSV en XML, il suffit de lire le fichier CSV avec la fonction fgetcsv et mettre les données dans des balisent XML : csv_to_xml.php

Une fichier CSV converti en XML ressemble à cela : channel.xml

Validation du fichier XML

Il ne reste plus qu’à écrire le schéma permettant de valider le fichier XML : channel.xsd

Vous pouvez tester vos schéma en ligne de commande en utilisant la commande xmllint issue du paquet Debian libxml2-utils :


$ xmllint --noout --schema channel.xsd channel.xml
channel.xml validates

Validation en PHP

Une fois que votre schéma est correct et permet de valider le contenu du fichier XML, il faut l’intégrer directement dans votre application : validate.php

Conculsion

Cette méthode apporte les avantages suivants :

  • Les règles de validation sont décrites directement via le schéma, et celui-ci dispose de nombreuses fonctionnalités tels les types de bases, les expression rationnelle, les séquences, etc…
  • Les messages d’erreur sont compréhensibles pour l’utilisateur même, s’il faut parfois les traduire (en convertissant les numéros de lignes et certaines notions) ;
  • La simplicité ;0)

Cette technique a été mise en oeuvre dans le cadre du projet MAVISE pour valider plus d’une vingtaine de formats de fichiers différents.

Filed under: Work — Tags:, , , — cscm @ 07:10

May 30, 2008

Système de synchronisation conditionnel pour PostgreSQL

Il existe des outils tel que SQLSync permettant de synchroniser les données d’une base PostgreSQL. Mais cette outil, ne permet pas de faire une copie partiel des données.
Dans la plus part des cas, il faut programmer la synchronisation soit même, l’objectif de mon article est de vous proposer une solution simple et élégante.

La problèmatique

Des données sensibles se trouvent dans la base principale et il ne faut synchoniser qu’une partie de ces données vers les autres bases.

L’architecture dispose des caractèristiques suivantes :

  • Le schèma est identique sur chaque base ;
  • Les opèrations d’écriture se font seulement sur la base principale ;
  • Les bases sont accessibles en réseau.

La solution

Pour résoudre le problème, j’ai testé plusieurs approches avant de retenir l’utilisation d’un fonction de hachage dont voici le principe :

  • chaque table doit disposer d’un identifiant unique. (champ id de type SERIAL) ;
  • un programme récupère le couple id et la sum de hachage pour chaque table de chaque base qui doit être synchroniseés ;
  • il compare le couple et si celui-ci est diffèrent alors les données sont mise à jour.

Implementation au niveau de la base de données

Le calcul de la somme de hachage se fait un niveau de la base de données à l’aide d’un fonction :

SELECT id,md5 FROM get_table_md5('nom_de_la_table');
 id |               md5
----+----------------------------------
  4 | 9f3bcd2fae528244669613ae0466cc3c
  5 | 1f3bcd2fa24528244669613ae66cc3czd
...
(42 row)

Voici le code de la function PL/Perl:

CREATE LANGUAGE plperl;
CREATE TYPE table_md5 AS (id INTEGER, md5 TEXT);
CREATE OR REPLACE FUNCTION get_table_md5(varchar) RETURNS SETOF table_md5 AS $$
    my ($rv, $status, $nrows, $row);
    # Get Table OID
    $rv = spi_exec_query("SELECT c.oid AS oid FROM pg_catalog.pg_class c
        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
        WHERE c.relname LIKE '$_[0]';");
    $status = $rv->{status};
    $nrows = $rv->{processed};
    return undef if ($nrows != 1);
    my $oid = $rv->{rows}[0]->{oid};
    # Get Table attributs
    $rv = spi_exec_query("SELECT attname,atttypid FROM pg_catalog.pg_attribute a
        WHERE a.attrelid = $oid AND a.attnum > 0 AND NOT a.attisdropped");
    $status = $rv->{status};
    $nrows = $rv->{processed};
    return undef if ($nrows < 1);
    my $atts;
    my $atts_not_null;
    foreach my $rn (0 .. $nrows - 1) {
        if ($rv->{rows}[$rn]->{atttypid} == 16) {
            $atts .= " (CASE WHEN $rv->{rows}[$rn]->{attname} THEN 't' ELSE 'f' END) AS $rv->{rows}[$rn]->{attname}";
        } else { 
            $atts .= " $rv->{rows}[$rn]->{attname}::text";
        }
        $atts_not_null .= " (CASE WHEN $rv->{rows}[$rn]->{attname} IS NULL THEN '' ELSE $rv->{rows}[$rn]->{attname} END)";
        $atts .= ',' if ($rn != $nrows - 1);
        $atts_not_null .= ' || ' if ($rn != $nrows - 1);
    }
    # Calc MD5
    my $sql = "SELECT id, MD5($atts_not_null) AS md5 FROM (SELECT $atts FROM $_[0]) AS $_[0];";
    $rv = spi_exec_query($sql);
    $nrows = $rv->{processed};
    foreach my $rn (0 .. $nrows - 1) {
        return_next({
            id  => $rv->{rows}[$rn]->{id},
            md5 => $rv->{rows}[$rn]->{md5}
        });
    }
    return undef;
$$ LANGUAGE plperl;

Pour utiliser cette fonction vous avez besoin du support du language de programmation PL/Perl dans Postgres, celui-ci se trouve dans le paquet Debian : postgresql-plperl-

La gestion des conditions

Le choix des données à synchroniser se fait individuellement pour chaque table à l’aide de la clause WHERE de la manère suivante :

SELECT id,md5 FROM get_table_md5('nom_de_la_table') WHERE id IN(SELECT id FROM nom_de_la_table WHERE nom_de_la_table.champ LIKE 'sync');
 id |               md5
----+----------------------------------
  1 | fdd56eabd4bb997e453e33f0022d46c1
(1 row)

Implementation du script de synchronisation

La synchronisation des données peut être réalisée à l’aide de n’importe quel language de programmation,
il suffit juste de disposer d’un accèss à la base de données.

Voici un exemple de script écrit par Emmanuel Saracco en PHP5 qui se repose sur les modules PEAR suivants :

  • DB : pour l’accès à la base de donnée.
  • Console_Getopt : pour la gestion de la ligne de commande.

Ce script nécessite un fichier de configuration, en voici un exemple.
Ce fichier contiens la configuration des diffèrentes base de données et les conditions sous-forme de clauses where.

Conclusion

Cette méthode de synchronisation fonctionne, elle est en production chez un client pour une base de données de plus de 100 tables, elle permet de synchronisé le contenu d’une base vers deux autres.
Les avantages de cette méthode sont les suivants :

  • le fait de pouvoir choisir le contenu de la clause where permet de réalisé des régles de synchronisation très complexes ;
  • les performances sont très bonnes car les transfert réseau sont limités ;
  • il est possible d’écrire le script de synchronisation avec n’importe quel language de programmation.
Filed under: Work — Tags:, , — cscm @ 14:30

Powered by WordPress