Université du 7 Novembre à Carthage Année universitaire 2006/2007 Institut National des Sciences Appliquées et de Technologie I.N.S.A.T Atelier de Génie Logiciel Avancée : ANT INTRODUCTION : Dans cet Atelier nous allons présenter possibilités de ant. un jeu d’activités illustrant les différentes INSTALLATION : Télécharger la distribution binaire de ant (www.ant.apache.org). Dé zipper le fichier apache-ant-1.6.5-bin.zip (un répertoire jakarta-ant-version est créer, contenant l'outil et sa documentation) . Ajouter le chemin complet du répertoire bin de Ant à la variable système PATH (pour pouvoir facilement appeler Ant n’ importe où dans l'arborescence du système). S'assurer que la variable JAVA_HOME pointe sur le répertoire contenant le JDK. Créer une variable d'environnement ANT_HOME qui pointe sur le répertoire jakarta-ant-version créé lors de la décompression du fichier. Il peut être nécessaire d'ajouter les fichiers .jar contenus dans le répertoire lib de Ant à la variable d'environnement CLASSPATH. Exemple 1 : Hello World Dans cet exemple nous allons développer un fichier « build.xml » qui affiche le message « Hello World! » Pour commencer nous allons créer un répertoire «d:/Ant_Projects/PR_HelloWorld » sous lequel on crée le fichier « build.xml » suivant : <?xml version="1.0"?> <project name="Exemple1" default="SayHello" basedir="."> <target name="SayHello"> <echo>Hello World!</echo> </target> </project> L’exécution du contenu de ce fichier est assurée par la commande : ant [options] [cible] Dans notre cas, si on est placé dans le même répertoire où se trouve « build.xml » : Ant –v Sinon : Ant –v –buildfile %PROJECT_PATH\build.xml L’options –v (Verbose) est utilisé pour fournir un maximum d’information lors de l’exécution tandis que l’option –buildfile est utilisé pour préciser le nom du fichier de configuration Nous aurons comme résultat : Comme vous remarquez, Ant affiche les différentes étapes d’exécutions (grâce à l’option -v) qui sont : - Détection de la version de java. Détection du SE. Parsing du fichier build.xml. Initialisation du répertoire racine du projet. Création des cibles (Dans notre cas «SayHello »). Affichage du résultat d’exécution des différentes cibles et de leurs tâches correspondantes. La plupart des IDE Java dispose de plugins pour utiliser ANT et c’est le cas pour Eclipse. Exécuter ANT à partir d'un IDE permet de bénéficier de fonctionnalités supplémentaires par rapport à un script : Vision synthétique de l'ensemble des cibles disponibles, avec leur description. Exécution d'une cible d'un simple clic de souris ou d'un raccourci clavier. Possibilité de suspendre temporairement l'exécution. Présentation de rapports d'exécution plus facilement exploitable (lien vers le code source, gestion en arborescence, possibilité de changer de niveau de log à posteriori, ...). Aide à la gestion du CLASSPATH. Reproduisons alors notre exemple sous eclipse. Pour cela il faut : Créer un nouveau projet java. Créer le fichier build.xml Afficher la vue de Ant (Window>Show View>Ant). Ajouter le fichier build.xml Sélectionner le fichier et appuyer sur pour exécuter. Nous aurons ainsi le résultat suivant : Exemple 2 : Hello World Dans cet exemple nous allons créer un projet dans lequel il y’aura une classe « HelloWorld» (qui affiche un message « HelloWorld! ») et notre fameux fichier « build.xml ». La tâche de ce fichier XML consiste à : Créer, s’il n’existe pas, un répertoire intitulé « output ». Compiler la classe et placer le fichier « HelloWorl.class » généré dans le répertoire « output ». Créer un fichier « Hello.jar » et le placer sous le répertoire « output ». Exécuter la classe compilée. Le fichier « build.xml » présente le code suivant : <project name="hello" default="Execute"> <!-- Définition du classpath du projet--> <path id="projet.classpath"> <pathelement location="./output/hello.jar" /> </path> <!--Création du répertoire output--> <available property="output.exists" file="./output"/> <!--Si le répertoire n'existe pas alors création--> <target name="Prepare" unless="output.exists"> <mkdir dir="./output" /> </target> <!--Sinon Afficher message--> <target name="RepExist" if="output.exists"> <echo message="Le répertoire output existe déja" /> </target> <!--Compilation de la classe--> <target name="Compile" depends="Prepare,RepExist"> <javac srcdir="." destdir="./output" /> </target> <!--Création du fichier hello.jar--> <target name="Packaging" description="Création du hello.jar" depends="Compile"> <jar destfile="./output/hello.jar"> <zipfileset dir="output" includes="HelloWorld.class"/> </jar> </target> <!--Exécution de la classe compilé--> <target name="Execute" depends="Packaging"> <java classname="HelloWorld" fork="true"> <classpath refid="projet.classpath"/> </java> </target> </project> Après avoir ajouter le fichier « build.xml » à la vue « Ant » nous aurons : Cette vue nous permet d’avoir une vision claire de notre fichier de configuration. En fait « built.xml » est constitué de cinq cibles (ou targets) que nous pouvons classer en trois groupes : Cibles de Préparation du répertoire destination : Prepare, RepExist Ces deux cibles ont pour objectif de créer, s’il n’existe pas, un répertoire « output », sinon afficher le message « Le répertoire output existe déjà ». Le tag <available> permet de tester si le répertoire output existe ou pas, si oui la propriété « output.exists » serait égale à true sinon false. Selon la valeur de « output.exists » le target « Prepare » ou « RepExist » sera exécuté. En effet, dans le target « Prepare » nous avons l’attribut unless donc cette cible serait exécutée en l’absence du répertoire « output » sinon le target PrepareExist (doté de l’attribut « if ») serait exécuté et afficherait le message « Le répertoire output existe déjà ». Tâches de Préparation des fichiers nécessaires : Compile, Packaging Le rôle de ces deux tâches est de créer le fichier « Hello.class » ainsi que le « hello.jar » qui sera nécessaires après pour l’exécution. Dans la cible (target) « Compile » la compilation de « Hello.java » est réalisé par la tâche <javac>. Cette tâche va compiler toutes les sources présentes dans le répertoire racine du projet. Les « .class » générés seront placés dans le répertoire « output ». Donc avant de compiler il faut que le répertoire destination soit créer, ce qui explique la présence de l’attribut « depends » dans la cible « Compile ». En fait cet attribut exige que les tâches « Prepare » et « RepExist » soient exécutées avec succès avant d’entamer l’exécution des tâches du target « Compile ». A condition que le target « Compile » soit exécuté avec succès (depends= ’Compile’ ), la cible « Packaging » va créer le « hello.jar », cela par l’intermédiaire du task <jar>, dans lequel nous avons spécifié l’emplacement du fichier à créer et les « .class » qui seront archivés. Tâche d’exécution : Execute Dans cette tâche nous allons exécuter la classe « Hello » déjà compilé. Avant exécution, il faut tout d’abord ajouter au classpath les différentes ressources nécessaires (dans notre cas « hello.jar »). Ceci est assuré par le tag <path> qui, à travers le sous tag <pathelement> va définir le « .jar » qui sera ajoutée à la variable classpath d’exécution. Donc l’exécution dépend de la tâche « Packaging ». Pour cela on trouve l’attribut depends égale à « Packaging » dans la cible « Execute ». La tâche « Execute » est assurée par le task <java> dans lequel nous avons spécifiés le nom de la classe à exécuter. L’attribut fork égale à « true» mentionne que l'exécution se fait dans une JVM dédiée au lieu de celle ou s'exécute « Ant ». La définition du classpath à utiliser lors de l’exécution est réalisée par le tag fils <classpath>. Le résultat obtenu après exécution du build.xml : Comme vous remarquez « Ant » commence par créer le répertoire output, ensuite compiler le fichier « hello.java », puis créer l’archive JAR et enfin exécute la classe qui affiche le message « Hello World ». La tâche PrepareExiste ne s’est pas exécutée car le répertoire « output » n’existe pas. Si on exécute une autre fois le fichier build.xml nous aurons : Vous remarquez donc que les tâches Prepare, Compile et Packaging ne se sont pas exécuté puisque le répertoire « output » existe déjà ainsi que « Hello.class » et « hello.jar ». La tâche PrepareExist s’est exécutée car le répertoire « output » existe déja. Exemple 3 : Construction d’Application J2EE (Calculette) La construction d’application J2EE est l’utilisation principale de Ant. L'intérêt d'un module de construction au sein d'une application est de garantir à tout moment une livraison fiable de l'application sous sa forme finale (.jar, .war, .ear, …) à partir de ses sources, voire son déploiement sur un serveur. Quand l'application est sensible, il devient inconcevable de réaliser l'installation manuellement en raison des risques d'oubli de livraison de fichiers ou d'opérations diverses (eg. adaptation des propriétés de configuration, correction des caractères spéciaux CR/LF, …). Le gain de temps apporté par une installation automatisée est également un facteur primordial, surtout quand le mode de développement choisi requiert un rythme d'intégration élevé (cf. processus d'intégration continue de la méthode Extreme Programming). Dans cette exemple nous allons découvrir les différentes facilités offertes par ANT dans le développement d’application J2EE. L’application J2EE que nous allons construire est composé de : Une page d’accueil « Index.html » qui présente un formulaire demandant la saisie de deux nombre. Une Servlet « CalculatriceServlet » qui va faire appel à un Session Bean pour calculer la somme des deux nombres saisies dans la page d’accueil. Un Session Bean « CalculBean » qui va s’occuper de la tâche de calcul de la somme. L’interface Home et Remote de Bean Session. Fichiers XML de configuration. Vue du projet sous eclipse : Remarque : - Pour le développement de cet application, nous avons choisie JBoss comme conteneur d’EJB. Dans cet exemple, Nous n’allons pas expliquer comment créer les différents composants, mais plutôt comment utiliser Ant pour automatiser la construction de l’application. STRUCTURE DU FICHIER DE CONFIGURATION ANT : La construction de l’application avec Ant consiste à créer le fichier « build.xml » dont la structure est la suivante : Donc la construction consiste principalement à compiler nos ressources (Servlets, EJBs…), ensuite créer les archives JAR, WAR et EAR et enfin déployer l’application sur le serveur. Déclaration des propriétés : Le tag <property> permet de définir une propriété qui pourra être utilisée dans le projet : c'est souvent la définition d'un répertoire ou d'une variable qui sera utilisée par certaines tâches. Leur définition en tant que propriété permet de facilement changer leur valeur une seule fois même si la valeur de la propriété est utilisée plusieurs fois dans le projet. Exemple la propriété « lib.Dir » a comme valeur le répertoire contenant les fichiers JAR nécessaires pour la compilation. Initialisation : La cible « init » commence par supprimer les fichiers générés suite à une exécution antérieur du fichier « build.xml ». Après suppression un répertoire « bin » sera crée. Notons ici l’utilisation des propriétés « output.Dir » et « deploy.Dir » qui ont pour valeurs respectivement «bin » et «D:\Program Files\jboss4.0.4.GA\server\default\deploy\ ». Compilation : Dans cette phase nous allons compiler les fichiers sources (EJB et Servlet). La cible « compile » qui dépend de la cible « init » va compiler les fichiers présents dans le répertoire source « src » (spécifié par la propriété « source.Dir »). Les « .class » seront placés dans le répertoire « bin » (spécifié par la propriété « source.Dir »). Les ressources utilisées dans la compilation sont les fichiers « .jar » présents dans le répertoire défini par la propriété « lib.Dir ». Packaging : CalculEJB.jar : La création de « CalculEJB.jar » est réalisée après la compilation des différentes classes. Ce fichier d’archive contiendra les fichiers de configuration (« ejb-jar.xml » et « jboss.xml ») ainsi que les « .class » du bean et ses interfaces qui se trouvent sous le répertoire « bin ». Il est annoté que les fichiers de configuration, qui se trouvent sous le répertoire « src/META-INF », seront archivés sous le répertoire « META-INF » (prefix = « META-INF »). CalculWEB.war : L’archive CalculWeb.war sera construit à partir de : Fichiers XML de configuration (web.xml et jboss-web.xml) qui se trouvent sous le répertoire « src/WEB-INF ». Le répertoire sous lequel seront archivés ces fichiers est « WEB-INF ». Servlet placée sous le répertoire « tutorial/web » et qui sera archivé sous le répertoire « WEB-INF/classes ». Fichiers placés sous le répertoire « docroot ». CalculApp.ear : C’est le dernier archive que nous allons générer il contient : CalculEJB.jar et CalculWeb.war Le fichier de configuration « application.xml » situé sous le répertoire « src/META-INF » et qui sera archivé sous le préfix « META-INF ». Déploiement : La phase de déploiement consiste à copier le fichier « CalculApp.ear » sous le répertoire de déploiement du serveur. Dans notre cas « D:\Program Files\jboss4.0.4.GA\server\default\deploy\ ». Vérifions maintenant le bon déroulement de l’exécution du « build.xml » : Exemple 4 : Création de tasks Ant Personnalisables Dans cet exemple nous allons créer une tâche Ant qui va générer un fichier personnalisé. Ce dernier va être utilisé par une autre application comme étant un fichier de configuration dans lequel nous trouverons les informations concernant les utilisateurs. Ces informations seront de type « Object ». Voici un exemple d’utilisation du task développé : Comme vous remarquez, il faut définir : D’abord, la tâche personnalisé, cela en précisant dans le task <taskdef> le nom de la tâche, le nom de sa classe et l’emplacement exacte du fichier compilé (.class) qui sont dans notre cas, respectivement « Config », « Custom_Config » et le répertoire racine du projet. Ensuite, les différents attributs de notre task : Dans cet exemple, le fichier de configuration généré sera intitulé « out.txt ». Ce fichier sera placé sous le répertoire « D:\Ant_Projects\ANT_CUSTOM_TAG ». Puis, les nœuds qui représentent les différents utilisateurs. Pour la création du task, il faut d’abord commencer par créer la classe : Custom_Config.java : import org.apache.tools.ant.BuildException; import java.io.*; import java.util.*; public class Custom_Config extends org.apache.tools.ant.Task{ private String APPDir = null; private ArrayList nodes = new ArrayList(); private String outputfile = null; public void setAPPDir(String APPDir) { this.APPDir = APPDir; } public void setOutputfile (String outputfile) { this.outputfile = outputfile ; } public void execute() throws BuildException { FileOutputStream fo = null; ObjectOutputStream oo = null; try { fo = new FileOutputStream(APPDir + "/"+outputfile); oo = new ObjectOutputStream(fo); oo.writeObject(nodes); oo.close(); fo.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void addConfiguredUser(User n) { nodes.add(n); } } La classe « Costum_Config » hérite de la classe « org.apache.tools.ant.Task ». Elle est constitué de : Les différents attributs qui seront spécifiés après dans l’appel de la tâche. Les Méthodes Setters des attributs de la tâche. La méthode execute() qui présente le cœur de la tâche à créer. Dans cette méthode il y a création du fichier de configuration et stockage des noeuds du task (Les informations concernant les utilisateurs). La méthode addConfiguredUser(User n) : Ant exécute cette méthode chaque fois qu’il rencontre un nœud. A chaque exécution le nœud rencontré va être ajouté à l’objet « nodes » de type ArrayList. Les informations que contiennent les nœuds seront stockées dans une classe User Serialisable. import java.io.*; public class User implements Serializable { private static final long serialVersionUID = -259332824550744321L; private int id = 0; private String username; private String password; public void setID(int id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public int getID() { return id; } public String getUsername() { return username; } public String getPassword() { return password; } } Cette classe est constitué des : Attributs du noeud user. Méthodes Getter et Setter correspondant à ces attributs. Testons maintenant notre task : Après exécution un fichier « out.txt » serait généré. Pour tester que les données stockés sont bien les données spécifiés dans le fichier XML, nous avons réalisé un petit programme qui va lire le fichier généré et extraire les données qu’il contient. import java.io.FileInputStream; import java.io.ObjectInputStream; import java.util.ArrayList; public class Recup_Conf { private static ArrayList nodes = new ArrayList(); public static void main(String[] args) { FileInputStream fo = null; ObjectInputStream oo = null; try { fo = new FileInputStream("out.txt"); oo = new ObjectInputStream(fo); nodes=(ArrayList) oo.readObject(); for (int i=0 ; i<nodes.size();i++) { User n=(User) nodes.get(i); System.out.println("ID : "+n.getID()+"\nUser Name : "+n.getUsername()+"\nPassword : "+n.getPassword()); System.out.println("------------------------------------------------------------"); } oo.close(); fo.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } Voici le résultat d’exécution de ce programme :