11 mars 2009

JBoss TattleTale, un outil de vérification de dépendances

Actuellement, j'effectue la migration vers Maven de quelques applications historiques. La difficulté de cette tâche réside dans le dépouillement et l'inventaire des fichiers JAR desquels dépend une application. En effet, on se retrouve face à un amas de fichiers JAR parmi lesquels se trouvent des fichiers JAR doublons (avec un nom différent ou une version différente), des fichiers JAR appartenant au serveur d'applications (par exemple, servlet.jar ou connector.jar), d'autres qui ne sont pas ou plus utilisés par l'application et parfois même des fichiers JAR de bibliothèques de tests unitaires (par exemple junit.jar). Le travail consiste alors à ne garder que les JAR qui sont réellement utilisés et d'éliminer (avec la plus grande précaution) tous les autres. Il va sans dire que s’attaquer à un tel problème à « mains nues » n'est pas une mince affaire.

C'est en cherchant un outil de vérification de dépendances que je suis tombé sur l'utilitaire « JBoss Tattletale », qui à ma grande chance vient de sortir (en version bêta) des laboratoires de JBoss (voir l'annonce sur le Blog de JBoss).
Cet outil permet de vérifier les dépendances entre les fichiers JAR présents dans un même répertoire (considéré comme un classpath). Ses fonctionnalités les plus remarquables sont :
  • Identifier les dépendances entre les fichiers JAR (par exemple, hibernate-3.1.3.jar dépendant de antlr-2.7.6rc1.jar)
  • Lister les classes dont dépend un fichier JAR et lister celles qu'il expose.
  • Lister les classes dont dépend un JAR, mais qui sont absentes du classpath.
  • Lister les classes présentes dans le classepath et les fichiers JAR dans lesquels elles se trouvent
  • Alerter si une classe est présente dans plusieurs fichiers JAR.

L'outil s'utilise en ligne de commande et sa prise en main est rapide. Il génère un rapport au format HTML dans lequel on peut naviguer aisément (voir la capture d'écran ci-contre).

Cet outil m'a vraiment aidé à effectuer la vérification des dépendances lors de la phase préliminaire de migration vers Maven. Malheureusement, il ne dispose pas (pour l'instant?) de plugin Maven permettant l'automatisation de cette tâche (de son coté, ANT dispose déjà d'une tâche tattletale).
Enfin, si vous vous demandez ce que veut dire «tattletale» sa définition se trouve ici. En résumé, «tattletale» est une personne commère qui révèle les secrets. On voit bien que e nom n'a pas été choisi par hasard !
L'outil, qui me semble très prometteur, en est tout juste à ces débuts. Souhaitons-lui un bel avenir!

Liens utiles :

Téléchargement : http://sourceforge.net/project/showfiles.php?group_id=22866&package_id=311046&release_id=665534 (800ko environ)

JIRA : https://jira.jboss.org/jira/browse/TTALE


6 mars 2009

Comment lire la version d'un JAR à partir du fichier « Manifest » ?


Toute application doit avoir un numéro de version. Il permet d'identifier aisément, entre autres, la branche du code source à l'origine de sa création (pour corriger des bogues, effectuer des évolutions, etc.). La technique la plus répandue c'est d'écrire le numéro de version dans un fichier de propriétés (properties) dont le contenu pourrait ressembler à ce qui suit :

application.version=1.0b

Il est alors possible à l'application d'extraire le numéro de version pour l'afficher dans la barre de titre, par exemple, ou l'utiliser dans la trace applicative. Ce procédé vous oblige à maintenir le fichier et à veiller à incrémenter correctement le numéro de version.

Bonne nouvelle si vous utilisez Maven : vous n’aurez plus à effectuer cette tache ! En effet, Maven inscrit systématiquement le numéro de version dans le Manifest (MANIFEST.MF) des JAR qu'il produit (le numéro de version est exactement le même que celui qui figure dans le POM du projet Maven).

Comment extraire le numéro de version ?

Commençons par un code simple. Cela se fait en deux étapes : charger le fichier MANIFEST.MF, puis lire son contenu grâce à la classe java.util.jar.Manifest.

La classe suivante est « packagée » dans un jar nommé version.jar.
public class VersionUtil {

public static void main(String[] args) throws IOException {
System.out.println(readVersion());
}

public static String readVersion() throws IOException {

InputStream in = VersionUtil.class.getResourceAsStream("/META-INF/MANIFEST.MF");

Manifest manifest = new Manifest(in);

// Lire la propriété "Implementation-Version" du Manifest

String version = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);

return version;

}
}

Le code ci-dessus fonctionne sans erreur, mais affiche quand même un résultat erroné : 1.5.0_10. Il correspond en réalité au numéro de version du JDK. Le ficher MANIFEST.MF chargé n'était «manifestement» pas celui du JAR en question, mais celui de rt.jar (le jar qui contient les classes de base de Java). L'explication est la suivante : la méthode getResourceAsStream(..) délègue la lecture de MANIFEST.MF au chargeur de classe (classloader) de la classe VersionUtil.class, or ce chargeur de classe se trouve dans rt.jar et non dans version.jar. Pour qu'une classe puisse charger le fichier MANIFEST.MF du JAR dans lequel elle se trouve, elle recourt à une méthode utilitaire qui calcule le chemin dudit JAR. Elle déduit ensuite l'emplacement du Manifest.

L'extrait de code Java montre comment le chemin vers le MANIFEST.MF est trouvé. J'avoue que le code est un peu alambiqué, mais heureusement il est commenté. La méthode présente l'avantage de fonctionner, et pour une classe se trouvant dans un jar, et pour une classe se trouvant dans un répertoire. Pour une meilleure compréhension, j'ai mis en commentaires le contenu des variables à chaque étape, et ce, pour les deux cas précités.
private static String getPathToManifest(){

// 1 - Lire le nom de la classe
String classSimpleName = VersionUtil.class.getSimpleName() + ".class";
// classSimpleName = VersionUtil.class

// 2 - Récupérer le chemin physique de la classe
String pathToClass = VersionUtil.class.getResource(classSimpleName).toString();

// pathToClass = file:/C:/workspace/VersionUtil/bin/com/abdennebi/version/VersionUtil.class
// pathToClass = jar:file:/C:/version.jar!/com/abdennebi/version/VersionUtil.class

// 3 - Récupérer le chemin de la classe à partir de la racine du classpath
String classFullName = VersionUtil.class.getName().replace('.', '/') + ".class";
// classFullName = com/abdennebi/version/VersionUtil.class

// 4 - Récupérer le chemin complet vers MANIFEST.MF
String pathToManifest = pathToClass.substring( 0, pathToClass.length() - (classFullName.length())) + "META-INF/MANIFEST.MF";
// pathToManifest = file:/C:/workspace/VersionUtil/bin/META-INF/MANIFEST.MF
// pathToManifest = jar:file:/C:/version.jar!/META-INF/MANIFEST.MF

return pathToManifest;
}
L'exemple complet se trouve à cet endroit VersionUtil.java. Une modification pour Java 1.4 se trouve ici : VersionUtil14.java.

16 janvier 2009

Maven, considérer le répertoire source comme un répertoire de ressources

Par défaut, Maven sépare les sources java des autres ressources, telles que les fichiers properties ou les fichiers XML. Ceci, constitue en général, une bonne pratique. Cependant, si vous migrez une application et que vous ne désirez pas séparer les ressources des sources Java, il est impératif d'en informer Maven, sinon vous risquerez d'avoir des surprises (un WAR sans le moindre fichier properties).

Pour ce faire, ajoutez ce bout de code XML au fichier POM de votre projet :


...


src/main/java

**/*.java



...

Cette déclaration informe Maven que le répertoire src/main/java contient des ressources et qu'il ne faut pas prendre en compte les fichiers Java (sinon ils risquent d'être inclus dans l'artéfact généré par Maven).

9 janvier 2009

Ajoutez un deuxième répertoire source à votre projet Maven

Actuellement, je m'occupe de la migration du code d'une application historique vers Maven. Cette application contient deux répertoires sources Java, l'un contient du code généré (des proxies PacBase), et l'autre contient le code écrit par le programmeur. Pour ce faire, il faut indiquer à Maven d'utiliser un répertoire supplémentaire à savoir src/main/javaproxie (en plus du classique src/main/java).

Maven permet de définir un répertoire source différent du répertoire source conventionnel grâce à la déclaration suivante (en prenant comme exemple src/main/javaproxies):


src/main/javaproxies


Le problème est qu'il n'est pas possible d'ajouter (nativement) un nouveau répertoire source. C'est plutôt le rôle du plugin build-helper et son goal add-source, il permet, en effet, de définir plusieurs répertoires sources de la manière suivante :




org.codehaus.mojo
build-helper-maven-plugin


add-source
generate-sources

add-source



src/main/javaproxies







En résumé, dans des situations où il est nécessaire d'utiliser un répertoire source supplémentaire, par exemple du code généré par xDoclet, des stubs et proxies Corba ou Web Services, ce plugin vous sauvera la vie !