Développez à la mode J2EE en utilisant .NET avec le SnippetStore  (Chapitre II) par Julien Brunet (j.brunet@cross-systems.com)

Chapitre 2/3
Impression papier : environ 26 pages
Version 1.1

Assemblage du framework SnippetStore

Ce chapitre décrit l'assemblage du framework SnippetStore à partir des sources disponibles sur SourceForge (informations de téléchargement en bas de ce chapitre). C'est également ici que vous trouverez le détail d'implémentation des différentes APIs de J2EE en utilisant celles de .NET.

- Si vous êtes arrivé ici par erreur, vous pouvez reprendre au chapitre 1.
- Si vous souhaitez voir à l'oeuvre le portage de Java PetStore utilisant ce framework, vous pouvez aller au chapitre 3. Il vous faudra néanmoins suivre les labs de ce chapitre pour fabriquer les binaires nécessaires à cette conversion.

Avant de commencer

Préparation des labs

Lab 1. Préparation de l'environnement SnippetStore

- Récupérez le package SnippetStore.zip [1] sur SourceForge, et dézippez-le dans C:\SnippetStore
- Récupérez et installez JDK 1.2.2 [2], J2EE 1.2.1 [3], Jad [4], NAnt [5], MSDE [6] et ASP.NET Web Matrix [7]
- Ouvrez un Command Prompt dans C:\SnippetStore
- Editez setenv.cmd et désignez les bons emplacements (JAVA_HOME, J2EE_HOME, NAntDir, JadDir)
- Exécutez setenv.cmd dans ce CommandPrompt
- Conservez le Command Prompt ouvert pendant toute la durée des labs

Téléchargement de Java PetStore

Lab 2. Téléchargement et installation de Java PetStore

- Récupérez le .zip de Java PetStore sur le site de Sun [8]
- Dézippez-le dans C:\SnippetStore\samples\jps1.1.2

Note: Vous devez vous enregistrer auprès de Sun pour pouvoir télécharger Java PetStore.

Conventions d'écriture

Dans la suite du document et dans les codes source associés, nous allons mélanger allègrement code .NET et Java/J#. Pour plus de clarté, la convention suivante sera appliquée aux fichiers sources :

Language Origine Fichiers Convention de namespaces
Java Java .java minuscules (javax.servlet.jsp)
J# .NET .jsl Pascal (SourceForge.SnippetStore.Xml)
C# .NET .cs Pascal (SourceForge.SnippetStore.Xml)

 

Cette convention naturelle possède le double avantage d'être respectueuse des standards de chaque framework, et de vous permettre d'identifier en un coup d'oeil l'origine du code source que vous étudiez.

Un SDK J2EE sous .NET

Comme en cuisine, c'est toujours la préparation des ingrédients qui est la plus importante... et la plus longue !
L'objectif de cette étape pour le moins fastidieuse est de recompiler le package j2ee.jar en J#.NET. Or c'est très loin d'être simple... (souvenez-vous de l'histoire d'Apollo 13 où 50 ingénieurs doivent trouver comment relier un conduit d'air rond dans un récupérateur carré...) Courage, la suite sera plus facile.

J# et JDK : le jeu des 7 erreurs

La modification de code source J2EE étant interdite, il est donc exclu de distribuer des logiciels commerciaux basés sur ce SDK pour .NET. Dommage! Tout cela commence très mal, mais il ne faut pas se décourager pour autant. Rappelez-vous que J# n'a pas pour vocation de supporter intégralement Java, mais simplement d'amener ses développeurs a faire la transition vers .NET en utilisant la syntaxe du langage. On peut espérer qu'avec la fin des procès en cours, Microsoft aille un peu plus loin dans le support de JDK plus récents. A ce sujet, Brian Keller, le Product Manager de J#, lance un appel à contribution [9] pour améliorer le langage, profitez-en.

Nouvelle de dernière minute: Microsoft vient de sortir le Supplemental UI Library for Visual J# .NET v1.1 [10]. Je n'ai pas eu le temps de regarder mais il semble que ce package réduise un peu l'écart avec le JDK 1.2

L.A.M.E. !

Afin de contourner les défaillances de J#, j'ai créé quelques classes de substitution dans l'espace de nom LAME (Let Another Missing Entrypoint :-) D'autres classes ont vu leur code modifié pour utiliser ces substituts. On y retrouve donc les modifications suivantes:

Assembly Action Classe Commentaire
j2se.dll Substitut LAME.Character Un constructeur supplémentaire est apparu dans J2SE
  Substitut LAME.ClassLoader Le ClassLoader de J# renvoie toujours null !
  Substitut LAME.ResourceBundle Qui connait le lien entre les RessourceBundle, les fichiers .properties et les resources .NET ?
  Substitut LAME.Vector2Collection Un opérateur de conversion est apparu dans J2SE
  Substitut LAME.Enumeration2Iterator Idem
j2ee.dll Dégradé javax.rmi.PortableRemoteObject Afin de ne pas embarquer tout CORBA !
  Dégradé javax.mail.internet.MimeUtility Idem
  Changé javax.servlet.http.HttpServlet Pour référencer LAME.ResourceBundle
  Changé com.sun.activation.registries.MailcapTokenizer Pour référencer LAME.Character
  Substitut LAME.MailcapTokenizer Un opérateur de conversion char/int est apparu dans J2SE

 

Ne vous faites pas d'illusion : S'il n'y a pas tant de changements que ça, c'est parce l'emprunte J2EE a été réduite à son strict minimum pour faire fonctionner Java PetStore. Les curieux pourront étudier la target j2ee-3 du script NAnt pour constater la chose.

Essayer de conserver PortableRemoteObject intact pendant le "déménagement" revient à emmener toute la maison, la toiture et les fondations...il vaut mieux scier les pieds du canapé plutôt que d'abattre un mur, non ?

 

Ou sont les sources de J2EE ?

Nulle part! Autant la librairie du JDK est livrée avec ses sources, autant le J2EE SDK est comme le .NET Framework : Nous n'avons que le bytecode final dans le fichier j2ee.jar qu'il va falloir décompiler. La chose est triviale avec un outil comme Jad par exemple.

PS : Afin de ne vexer personne, jetez un coup d'oeil à Annakrino [11] pour .NET :-)

 

Solution

Ayant fait le tour des problèmes, voici le processus de conversion à mettre en oeuvre:

  1. On dézippe les sources du J2SE SDK
  2. On compile certains de ces sources avec les substituts LAME
  3. On dézippe les classes bytecode du J2EE SDK
  4. On décompile ces classes bytecode (les sources ne sont pas fournis)
  5. On compile certains de ces sources avec les substituts LAME
Lab 3. Conversion du J2EE SDK sous .NET

C:\SnippetStore> nant snk
C:\SnippetStore> nant j2se-1 j2se-2
C:\SnippetStore> nant j2ee-1 j2ee-2 j2ee-3

Le tour est joué ! Notre J2EE SDK est très incomplet, mais suffisant pour faire tourner Java PetStore.

Le plus dur est fait. Nous allons maintenant implémenter les couches de J2EE une par une.

Un parser XML

JAXP dans J2EE

J2EE contient en standard les interfaces DOM et SAX du W3C. Chose surprenante, la factory d'instanciation est définie dans un package séparé, JAXP (Java Api for Xml Processing). Cette factory permet au développeur J2EE de choisir l'implémentation à utiliser, parmi celles disponibles sur le marché.

JAXP est inclus dans le zip de PetStore, c'est pourquoi je vous ai demandé de le télécharger dès le début de ce chapitre.

System.Xml dans .NET

Le .NET Framework fournit une implémentation complète de parsers DOM et SAX. En revanche, aucune interface, que des classes ! Impossible donc d'envisager l'utilisation d'une implémentation différente sans changer votre code source.

Conversion de JAXP en .NET

Pour convertir jaxp.jar en .NET, c'est très simple : Le Framework SDK 1.1 fournit un nouvel outil, "JbImp.exe" (Microsoft (R) Java-language bytecode to MSIL converter). Cet outil convertit directement le bytecode java en MSIL .NET (sans passer par une phase intermédiaire de code source).

Si seulement le JDK de J# était à jour et complètement implémenté, la conversion du SDK J2EE aurait été aussi simple. Hélas, le "gap" est tellement important que JbImp s'arrête avec des internal errors ...

Lab 4. Conversion de JAXP

C:\SnippetStore> nant jaxp

 

SnippetStore.Xml : Le pont JAXP - System.Xml

C'est ici que les choses sérieuses commencent : Il s'agit d'implémenter une interface (Java) en délégant sur une classe agrégée (.NET). Le principe est bien connu des programmeurs Delphi, qui disposent même d'un mot-clef du langage pour ça !

Heureusement, les deux APIs J2EE et .NET sont toutes les deux basées sur la spécification W3C, la correspondance de classes est donc très facile à établir.

L'approche des wrappers est très naïve : Lorsqu'une méthode Java renvoie une interface, on réinstancie sans réfléchir le wrapper correspondant, même s'il existe déjà. Cette méthode a le mérite d'être performante, car la vitesse de construction du wrapper est supérieure au calcul qu'il faudrait établir pour déterminer l'existence d'un wrapper existant.

En .NET, le garbage collector dispose d'un algorithme mark-and-sweep, c'est-à-dire qu'il alloue la mémoire de manière linéaire et la compacte régulièrement à la façon d'un défragmenteur. L'instanciation d'un objet en .NET est donc une des opérations les plus rapides du Framework. De nombreux articles expliquent le sujet en détail.

Lab 5. implémentation de JAXP en .NET

C:\SnippetStore> nant SnippetStore.Xml

L'accès aux données

JDBC dans J2EE

L'API JDBC (Java DataBase Connectivity) ne fait pas partie de J2EE mais du JDK. Elle est constituée principalement d'interfaces Java dédiées à l'accès aux données. Il suffit donc, comme pour JAXP, d'implémenter ces interfaces pour fournir un nouveau provider.

Le saviez-vous ? J# fournit en standard une implémentation générique sur ODBC (héritée de Visual J++ 6.0). Elle est située dans l'espace de nom com.ms.jdbc.odbc de l'assembly vjslib.dll

 

ADO.NET

L'API ADO.NET (Active Data Objects.NET) est également constituée d'interfaces (IDbConnection, IDbCommand...). De multiples providers sont disponibles en standard, certains accédant à des middlewares génériques (OleDb, Odbc), d'autres spécialisés (SqlClient, OracleClient, MySql...) à la manière des thin-drivers JDBC.

La conception d'ADO.NET est très élégante, sauf sur un point : il n'existe pas en standard de factory d'instanciation, ce qui veut dire qu'à moins de la constituer par vous-même (la chose est relativement triviale), vous devez explicitement manipuler les classes d'un provider X ou Y.

 

SnippetStore.Data : Le pont JDBC - ADO.NET

Passer par ODBC ... pourquoi pas, mais le challenge est faible. Je vous propose donc de réimplémenter nous-même les interfaces JDBC sur une base ADO.NET. Le mapping est plus délicat, mais une bonne connaissance de chaque framework permet rapidement de déterminer les correspondances.

Le pattern reste le même : l'implémentation des interfaces J2EE par les wrappers agrège en interne les interfaces ADO.NET, lesquelles référencent elles-même une implémentation (OleDb par défaut)

Attention! Une petite anecdote sur le pont (constatée en exécutant PetStore) :

- en Java, on peut appeler récupérer plusieurs ResultSet depuis des appels à executeQuery() sur le même Statement
- en .NET, il ne peut y avoir qu'un seul DataReader actif par appel à ExecuteReader() sur le même DbCommand

Le wrapper fait donc un suivi du dernier DataReader généré par la DbCommand interne et le ferme avant d'en fournir un autre.

 

Lab 6. Implémentation JDBC en .NET

C:\SnippetStore> nant SnippetStore.Data

L'annuaire

JNDI dans J2EE

L'API JNDI (Java Naming Directory Interface) est un élément majeur de J2EE, car elle lui donne l'essentiel de sa flexibilité. Disons pour simplifier que cette interface décrit un système d'annuaire distribuable sur lequel on peut référencer et obtenir aussi bien des objets Java que des entrées LDAP.

Dans le cadre d'applications J2EE comme Java PetStore, C'est le conteneur qui configure l'annuaire JNDI et qui y inscrit entre autres les objets et valeurs décrits dans les manifestes XML (que nous avons vu au chapitre 1). Les applications n'ont qu'à requêter l'annuaire avec les APIs JNDI pour récupérer ces objets et les utiliser dans l'application.

Voici un extrait de code de Java PetStore qui permet de comprendre l'intérêt d'un annuaire pour les factories :

Ce code permet de récupérer l'objet DAO (Data Access Object) de gestion de comptes,  via la classe JNDI InitialContext. L'avantage de passer par une factory faisant un lookup sur l'annuaire est que le conteneur peut binder une implémentation différente sans que cette factory n'en soit impactée. Dans Java PetStore, trois implémentations de DAO sont fournies : Cloudscape, Sybase et Oracle.

PS : Cet extrait de code illustre au passage ce que je considère comme les méfaits des exceptions checkées de Java : Les exceptions sont étouffées pour être remplacées par des exceptions plus générales (AccountDAOSysException). Nous reviendrons sur cet épineux problème à la fin du chapitre.

 

System.Directory dans .NET

L'API System.Directory de .NET est un wrapper sur le client Active Directory de Windows (Active Directory étant l'annuaire d'entreprise sur lequel toute l'infrastructure Microsoft s'appuie). Malheureusement, le référencement d'objets offert par JNDI est totalement absent de .NET !

 

Le pont JNDI - .NET

Il est donc clair que System.Directory n'est pas du tout adapté à un portage de JNDI puisqu'il lui enlève sa fonction principale, et pourtant très simple dans le cas de Java PetStore : maintenir une liste objets référencés par des identifiants chaîne uniques.

Pour implémenter JNDI, j'ai donc fait au plus simple : Une hashtable ! Certes, elle est très loin de gèrer toutes les finesses de contexte et de providers proposées par JNDI, mais cela suffit pour les fonctions bind/lookup de Java PetStore.

A part ce bricolage, l'utilisation de cette implémentation JNDI est la même que dans J2EE : Lorsque le conteneur Web démarre (nous le verrons plus loin), il monte un JNDI local et y publie les objets et valeurs des manifestes qu'il a chargé.

Le conteneur métier

Quatrième et avant-dernière partie de J2EE à implémenter dans le cadre de PetStore, les EJBs (Enterprise Java Beans). Pour cette partie comme pour la suivante (le conteneur Web), le travail à effectuer est beaucoup plus délicat : Autant JAXP, JDBC et JNDI sont des parties de code à wrapper une fois pour toutes, autant les interfaces et classes de cette API sont étendues par le développeur J2EE d'une part, par le serveur d'application d'autre part (comme nous allons le voir, le coeur du système EJB repose sur la génération dynamique de code)

Pour toutes ces raisons, nous allons devoir définir des principes de wrappers, et automatiser leur écriture pour chaque EJB via la toolkit. Pour que la chose soit compréhensible, il faut descendre un peu plus profondément dans les modèles objets des deux plateformes.

EJBs dans J2EE

Les EJBs sont au coeur de J2EE, puisqu'ils sont en charge des services transactionnels, de montée en charge et de répartition de composants.

La spécification EJB distingue 3 types de composants:

 

De plus, parmi les EJB entités, on distingue la façon dont la persistance est prise en charge :

 

D'un point de vue strictement technique, il s'agit d'un design pattern particulier destiné à faciliter le "tissage" des aspects (gestion des transactions, appels réseau, activation juste-à-temps...) par le conteneur J2EE. Le schéma suivant illustre quelques-uns de ces aspects:

 

Le Design Pattern EJB par l'exemple

Le Design Pattern EJB étant difficile à résumer, regardons ensemble le code de l'EJB "Account" de Java PetStore. Dans un développement EJB, il faut rédiger 2 interfaces et 1 classe pour chaque composant EJB :

  1. L'interface Home décrit la factory du composant. On y décrit les signatures de construction de nouvelles instances (create) et de sélection (findByPrimaryKey)
  2. L'interface de l'EJB décrit les opérations du composant. C'est uniquement par cette interface que le composant sera manipulé par la suite.
  3. L'implémentation de l'EJB doit contenir :
    - L'implémentation de l'interface de l'EJB (par correspondance de noms préfixés par "ejb")
    - L'implémentation de l'interface Home (par correspondance de noms préfixés par "ejb")
    - L'implémentation de l'interface EntityBean (ou SessionBean)
     

Tout cela est, d'un strict point de vue objet, très déconcertant (pour ne pas dire carrément hideux) ! On aurait pu penser que l'implémentation de l'EJB implémente avant tout l'interface du même EJB et que les interfaces "techniques" (EntityBean/SessionBean) soient éventuellement tissées autour... et bien non, c'est le contraire ! Heureusement, la plupart des environnements de développement J2EE assurent les contrôles de cohérence que le modèle objet ne fournit pas...

Comme si ce n'était pas déjà assez compliqué, il faut également décrire dans le manifeste de l'EJB les associations entre les trois éléments et leurs méthodes. Notez enfin l'indication des méthodes transactionnelles (<trans-attribute>)

 

Il reste deux questions en suspend :

  1. Qui implémente l'interface Home ? Réponse : Le conteneur J2EE, par tissage.
  2. L'interface de l'EJB hérite de EJBObject, mais l'implémentation de l'EJB hérite de Entity/SessionBean. Qui implémente l'interface EJBObject ? Réponse : Le conteneur J2EE, par tissage.

 

Serviced Components dans .NET

Soyons francs, même si dans le principe les Serviced Components de .NET s'apparentent aux EJBs de J2EE, leur périmètre technique est loin d'être comparable. Les Serviced Components représentent, pour l'heure, une intégration "raisonnablement élégante" des services COM+ (Common Object Model) qui existaient bien avant .NET sur la plateforme Windows. Ces derniers ont pour charge les services transactionnels, de montée en charge et de répartition de composants.

Dans .NET, les Serviced Components ne cachent pas leur inspiration J2EE puisqu'ils sont situés dans l'espace de nom "System.EnterpriseServices". C'est pourtant ignorer que, historiquement, les EJBs sont apparus après les composants COM+ (MTS, à l'époque) !

Comparativement aux EJBs, les ServicedComponents ne couvrent pas les aspects suivants:

Le schéma suivant illustre la classe de base ServicedComponent à dériver, avec un de ses aspects :

Oui, vous avez bien lu : La classe de base est imposée, ce qui ne va pas faciliter le tissage par la toolkit!

Coté transactionnel, la déclaration est beaucoup plus élégante : .NET utilise des attributs pour déclarer les composants transactionnels, directement dans le code. D'autres aspects sont également décrits de cette manière : Activation Juste-A-Temps, Pooling, Monitorat...

 

SnippetStore.EJB : Le pont EJB - ServicedComponent

Ce long résumé des fonctionnalités comparées des EJBs et des ServicedComponent met en lumière les différences notables entre les deux plateformes. L'API EJB est riche et souple, mais son modèle objet est inutilement complexe. La plateforme .NET est beaucoup plus simple a priori, mais également plus limitée. Pragmatique, diront certains :-)

Le pont EJB/ServicedComponent procède globalement par tissage entre les classes/interfaces et le manifeste EJB, comme suit :

La conversion proposée est donc simple, se limitant au strict nécessaire requis par Java PetStore. Le modèle CMP n'est pas pris en charge (Container Managed Persistence), puisque l'application n'utilise que le modèle BMP (elle gère elle-même sa persistance).

En guise de consolation, la toolkit SnippetStore propose non pas un mais deux modes de tissage, au choix :

  1. Un tissage "classique", qui génère des classes .NET standard, sans aucun lien avec la plateforme COM+
    Dans ce schéma, la toolkit dérive la classe d'implémentation de l'EJB en lui tissant les méthodes de l'interface Home et de l'interface de l'EJB.

  2. Un tissage "serviced", qui génère des classes .NET dérivées de ServicedComponent, s'appuyant donc sur la plateforme COM+
    Dans ce schéma, la toolkit se comporte à l'identique, mais elle est contrainte d'agréger en interne la classe d'implémentation de l'EJB et de redéclarer ses méthodes à signature équivalente.



    Les limitations suivantes s'appliquent :

    - L'attribut transactionnel est défini au niveau de chaque classe et non chaque méthode (limite de la plateforme)
    - L'activation Juste-A-Temps n'est pas disponible (n'ayant pas de moyen simple de gérer l'état)
    - Les ServicedComponents sont à chargement Library (intra-processus) exclusivement. En effet, le modèle Server nécessaire pour répartir ses composants sur plusieurs machines/processus nécessiterait de répartir également les autres APIs du conteneur, notamment l'annuaire JNDI.

Si vous utilisez le tissage serviced, vous aurez l'agréable surprise de voir vos composants COM+ s'inscrire automatiquement à l'exécution de Java PetStore, fonctionnalité intégrée du .NET Framework.

NB: Le choix du modèle de tissage se définit dans le script NAnt, propriété "serviced". Ce réglage n'affecte pas la compilation du lab ci-dessous mais la génération de code dans le chapitre 3

Lab 7. Implémentation EJB en .NET

C:\SnippetStore> nant SnippetStore.EJB

Le conteneur web

Fatigués ? Déçus ? Je vous comprends, ce wrapper EJB/COM+ est probablement très éloigné de vos espérances... mais soyons raisonnables : personne n'a dit que la chose était facile. Pour le conteneur Web, en revanche, ce sera plus simple ; par un heureux hasard, .NET et J2EE ont des ressemblances bien plus prononcées, dans le fond comme dans la forme.

Comme précédemment, nous allons étudier dans les grandes lignes les spécifications de chaque plateforme, pour en déduire par la suite la méthode d'interposition adéquate.

 

Le conteneur Web dans J2EE : Servlets, JSP et TagLibs

A l'inverse des EJBs, le conteneur Web dans J2EE a été précurseur. Il a d'ailleurs longtemps gardé une longueur d'avance sur son concurrent de l'époque, ASP 3.0 de Microsoft. Étant entendu que le rôle principal d'un conteneur Web est de générer des pages Web dynamiques, on distingue pour ce faire les trois briques suivantes parmi les APIs J2EE :

  1. Servlets : Ce sont des classes Java, rédigées par les développeurs, chargées de décoder une requête Web et de fabriquer une réponse appropriée, un peu à la manière des CGI d'antan.



    Les Servlets sont ensuites référencées dans un manifeste XML (comme partout dans J2EE), avec leur chemin de mapping virtuel au sein du conteneur web.



    Les Servlets jouent la plupart du temps le rôle de contrôleur Web, c'est-à-dire qu'elles ne produisent pas directement de réponse HTML mais préparent et analysent le contexte associé à la requête entrante.
     
  2. JSP (Java Server Pages) : Ce sont des pages Web (HTML, cHTML, WML...), rédigées par les développeurs, incorporant des blocs de code Java chargés d'altérer la sortie. A la première exécution, le conteneur Web J2EE procède par tissage et fabrique dynamiquement une servlet pour générer la réponse Web.



    Les pages JSP sont censées, si elles sont bien programmées, déléguer les traitements de code plus complexes à des classes JavaBeans, dont la déclaration se fait comme suit :


    Cette déclaration associe la variable locale "cart" à un JavaBean, stocké dans la session Web.
     
  3. TagLibs (Tag Libraries) : Ce sont des classes Java, réutilisables, que l'on peut facilement incorporer dans les pages JSP, afin de factoriser certains traitements répétitifs (listes paginées, etc...). Dans le code JSP suivant, les balises j2ee référencent la taglib déclarée dans l'entête de la page JSP :



    Techniquement, l'écriture d'un Tag nécessite d'implémenter l'interface javax.servlet.jsp.tagext.Tag (ou BodyTag).
    - La méthode doStartTag() est appelée lorsqu'une balise ouvrante est invoquée dans la page JSP. Elle doit renvoyer un énuméré qui indique si le contenu avant la balise fermante doit être ignoré.
    - La méthode doEndTag() est appelée lorsqu'une balise fermante est invoquée dans la page JSP
    - Les balises sont assemblées en structure arborescente via la méthode setParent()



    Vous devrez également pour utiliser une TagLib rédiger un manifeste (encore un!). Le schéma suivant illustre, par ses associations de couleurs, la relation tripartite entre un manifeste TagLib (1),  une page JSP (2) et une classe TagLib (3).



    - La balise @taglib détermine l'association entre le préfixe "j2ee" et le manifeste.
    - Ce manifeste détermine à son tour l'association avec des classes Java, le suffixe "prodDetailsAttr", et les propriétés JavaBeans telles que "attribute".

Les trois briques (Servlets, JSP, TagLibs) ne sont pas mutuellement exclusives : Java PetStore, à l'instar des recommandations J2EE, combine ces trois techniques au sein du modèle MVC (Modèle Vue Contrôleur).
 

Le conteneur Web dans .NET : HTTPHandlers, ASPx et WebControls

On distingue 3 briques de génération de pages dynamiques sous .NET

Attention, toute ressemblance avec des concepts déjà vus ne serait que pure coïncidence :-)

 

  1. HTTPHandlers : Ce sont des classes .NET, rédigées par les développeurs, chargées de décoder une requête Web et de fabriquer une réponse appropriée, un peu à la manière des CGI d'antan.

    Contrairement aux Servlets, il s'agit d'une interface à implémenter.
    .NET, One point (attention j'ai déposé ce slogan en or :-)



    Les HTTPHandlers sont ensuite référencés dans le fichier de configuration Web.Config (comme toujours dans ASP.NET), avec leur chemin de mapping virtuel au sein du conteneur web.



    Les HTTPHandlers jouent la plupart du temps le rôle de contrôleur Web, c'est-à-dire qu'ils ne produisent pas directement de réponse HTML mais préparent et analysent le contexte associé à la requête entrante.
     
  2. Pages ASPx : Ce sont des pages Web (HTML, cHTML, WML...), rédigées par les développeurs, incorporant des blocs de code .NET (ici C#) chargés d'altérer la sortie. A la première exécution de cette page, le conteneur Web ASP.NET procède par tissage et fabrique dynamiquement un HTTPHandler pour générer la réponse Web.



    Les pages ASPx sont transformées en classes .NET (implémentant IHTTPHandler). Elles sont censées, si elles sont bien programmées, déléguer les traitements de code à une classe sous-jacente (appelée CodeBehind) elle-même héritée d'une classe de base d'ASP.NET.

    Il n'existe pas d'équivalent .NET permettant d'associer automatiquement une variable locale à un composant en session.
  3. WebControls : Ce sont des classes .NET, réutilisables, que l'on peut facilement incorporer dans les pages ASPx, afin de factoriser certains traitements répétitifs (listes paginées, etc...). Dans le code ASPx suivant, les balises j2ee référencent la bibliothèque de WebControls déclarée dans l'entête de la page ASPx.



    Techniquement, l'écriture d'un WebControl nécessite d'hériter de la classe System.Web.UI.Control (ou un de ses dérivés).
    - La méthode virtuelle RenderControl() est appelée pour chaque paire de balise ouvrante/fermante invoquées dans la page ASPx. Elle doit appeler sa méthode ancêtre si elle souhaite inclure le contenu entre les deux balises.
    - La classe HtmlTextWriter passée en paramètre permet d'abstraire la génération de la réponse Web.
    - Les balises sont assemblées en structure arborescente via la propriété Parent.



    Contrairement aux TagLibs, il s'agit d'une classe à dériver.
    J2EE, One point :-)

    L'utilisation d'un WebControl dans une page ASPx est plus simple qu'en J2EE. Le schéma suivant illustre, par ses associations de couleurs, la relation bipartite entre une page ASPx (1), et une classe WebControl (2).



    - La directive @Register associe le préfixe "j2ee" au namespace "estore.AL" de l'assembly "estore.AL"
    - Le suffixe "prodDetailsAttr" utilisé dans la page correspond au nom de la classe dans le namespace.
    - Les attributs utilisés dans la page ASPx correspondent aux propriétés du WebControl.
    - Pour être pris en compte, l'attribut runat="server" doit être systématiquement ajouté à chaque balise WebControl.

 

SnippetStore.Web : Le pont Servlets/JSP/TagLib - HTTPHandlers/ASPX/WebControls

Les correspondances entre les concepts J2EE et .NET apparaissent immédiatement, pourtant vous aurez noté quelques différences d'approche. La divergence vient essentiellement dans le mode de traitement des pages JSP et ASPx, et nous pouvons a ce sujet établir un parallèle un peu osé avec les parsers XML :

- Les taglibs de J2EE décident de la génération du flux à la manière d'un parser SAX (invocations "événementielles" sur les balises ouvrantes et fermantes)

- Les WebControls  de .NET s'assemblent en modèle composite, à la manière d'un parser DOM (invocation sur l'élément racine qui itère sur ses sous-éléments)

Cette remarque étant faite, nous pouvons pour le reste établir un diagramme d'interposition. Le mapping étant plus compliqué que pour JAXP et JDBC, l'illustration ci-dessous a été simplifiée :

 

La Toolkit de conversion Web

Techniquement, la toolkit de conversion SnippetStore comprend trois parties :

1. 2. 3.
  1. Un convertisseur Servlet -> IHTTPHandler. Ce convertisseur lit le manifeste webapp et génère les IHTTPHandlers d'interposition. Il a également à charge de renseigner le Web.config avec les mappings adéquats.
  2. Un convertisseur TagLib -> WebControls. Ce convertisseur lit le manifeste taglib et génère les WebControls d'interposition.
  3. Un convertisseur JSP -> ASPx. Ce convertisseur à base de pattern-matching naïf transforme les pages JSP en pages ASP.NET (en J#). Il applique les transformations suivantes :
    - Directives @page, @taglib et @include (remplacement par les directives équivalentes ASP.NET)
    - Directives JSP jsp:useBean, jsp:getProperty, jsp:setProperty (injection de script serveur J#)
    - Directives TagLib (ajout du runat=server et correspondance avec les WebControls générés)

    Le saviez-vous ? ASP.NET recèle dans l'assembly non documenté System.Web.RegularExpressions une douzaine de regular expressions utilisées pour parser les pages ASP.NET. Certaines d'entre elles marchent aussi bien avec du JSP !

    Le saviez-vous (bis) ? Le namespace System.Text.RegularExpressions a été développé à l'origine par l'équipe en charge d'ASP.NET dans le framework.

 

Les limites du convertisseur JSP->ASP.NET

Honnêtement, le convertisseur JSP->ASP.NET est très insuffisant, mais il a au moins le mérite de faciliter la migration. Les problèmes suivants ne sont pas résolus notamment (ils ont été constatés lors de la migration Java PetStore) :

- La portée d'un bloc <% ... %> s'étend à la page JSP, alors qu'elle se limite au bloc en ASP.NET. Si une variable est déclarée dans un bloc JSP et utilisée dans un autre, il faut transformer le premier bloc en section <script runat="server"> ... </script>

- Les WebControls ne supportent une initialisation de leurs propriétés à partir d'expressions de code, mais seulement à partir de constantes. Partout ou des expressions sont employées avec des taglibs, il faut donner un identifiant unique au Tag/WebControl (attribut ID) et déplacer l'initialisation dans l'événement Page_Load() de la page ASP.NET

- Certaines imbrications de TagLibs ne sont pas correctement analysées par le parser ASP.NET.

- Enfin (le pire pour la fin), le problème des exceptions checkées, qui mérite un paragraphe... que voici :

 

Les exceptions checkées en .NET

Une des divergences notables entre Java et .NET tourne autour du concept d'exceptions checkées :

- En Java, les exceptions sont checkées. Cela signifie qu'une méthode doit déclarer explicitement au niveau de sa signature les types d'exceptions qu'elle peut lever, via le mot-clef throws (à ne pas confondre avec throw).

 

- En .NET, les exceptions ne sont pas checkées. N'importe quelle méthode peut lever n'importe quelle exception, sans que cela soit explicitement décrit dans sa signature. Cette définition est imposée par la CLS (Common Language Specification), le contrat en principe commun a tous les langages .NET

 

- J#, hybride entre les deux mondes, est un langage .NET qui supporte les exceptions checkées de Java. Comment est-ce possible? Le compilateur exige la présence du mot-clef throws, en accord avec le standard Java, et génère discrètement un attribut de méta-donnée .NET (com.ms.vjsharp.cor.JavaThrownExceptions, non documenté). Il lui suffit ensuite de relire cet attribut par réflexion pour simuler à la compilation le concept d'exceptions checkées.

 

Le problème de J# se situe au niveau de CodeDom. Or ce dernier est utilisé par ASP.NET ! En effet, l'API CodeDom, qui permet en .NET de générer du code en manipulant des objets symbolisant du code (classe, méthode), suit strictement la CLS... Impossible donc de lui indiquer la clause throws nécessaire au compilateur Java.

On imagine le choix cornélien qu'a du faire l'équipe J#... il n'y a en effet pas de "bonne" réponse, selon l'usage que l'on veut faire du langage... notons l'effort honorable mais néanmoins dérisoire de l'implémentation J# de CodeDom qui s'attache à "deviner" la clause throws en détectant la présence d'un throw dans le corps d'une méthode CodeDom (cela ne marche pas à tous les coups).
La seule chose regrettable, c'est de ne pouvoir indiquer au compilateur qu'on veut ignorer les exceptions checkées dans la génération CodeDom ou dans les pages ASP.NET, via un attribut de la directive de page de type "checkedexceptions=false" par exemple. En l'absence de cette fonctionnalité, vous devrez remplacer toutes les expressions ASP.NET <%= ... %> par un bloc <script runat=server> try { ... } catch (Exception e) { }; />. Cette limite s'applique au convertisseur JSP->ASP.NET

Lab 8. Implémentation Servlets/JSP/TagLibs en .NET

C:\SnippetStore> nant SnippetStore.Web

 

Une petite pause ?

Oui, vous l'avez bien méritée ! Ainsi s'achève ce chapitre 2, très long je vous l'accorde.

En guise de récompense, je vous propose de clore le sujet avec le chapitre 3, beaucoup plus simple, puisqu'il ne s'agit que de travaux pratiques : la conversion de Java PetStore sous .NET !

 


Auteur : Julien Brunet

Copyright © Juin 2003

Téléchargez les sources et les binaires

Site officiel SnippetStore : http://snippetstore.sourceforge.net/

[1] SnippetStore.zip (sources) : Snippetstore (Fichier zip)

Ressources

[2] JDK 1.2.2 - http://java.sun.com/products/jdk/1.2/download.html
[3] J2EE 1.2.1 - http://java.sun.com/j2ee/1.2/download.html#sdk
[4] Jad - http://kpdus.tripod.com/jad.html
[5] NAnt - http://nant.sourceforge.net/
[6] MSDE - http://www.asp.net/msde/default.aspx?tabindex=0&tabid=1
[7] ASP.NET Web Matrix - http://www.asp.net/webmatrix/default.aspx?tabIndex=4&tabId=46
[8] Java Pet Store - http://java.sun.com/blueprints/code/index.html#java_pet_store_demo
[9] Appel à contribution Brian Keller - http://msdn.microsoft.com/vjsharp/productinfo/survey/default.aspx
[10] Supplemental UI Library for Visual J# .NET v1.1 - http://msdn.microsoft.com/vjsharp/supui/
[11] Anakrinno - http://www.saurik.com/net/exemplar/

Licence GPL

Tous fichiers sont fournis sous la licence Lesser-GPL.