| 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
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.
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
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.
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.
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.
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
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 :-)
Ayant fait le tour des problèmes, voici le processus de conversion à mettre en oeuvre:
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.
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.
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.
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
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'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
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.
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'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.
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 !
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é.
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.
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 é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 :
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 :
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...
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 :
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
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.
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 :
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).
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 :-)
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 :
Techniquement, la toolkit de conversion SnippetStore comprend trois parties :
| 1. |
2. |
3. |
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 :
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
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.