Dopo l'articolo introduttivo del mese scorso, proseguiamo questo mese con la presentazione di un file di build di esempio che raccoglie le più comuni operazioni necessarie durante lo sviluppo di un progetto JavaEE. Non abbiamo certamente la pretesa di presentare una soluzione universale adatta a ogni programmatore e ogni caso specifico, ma piuttosto fornire gli spunti da cui poi partire per creare file di build personalizzati e specifici per le varie esigenze.
Un build.xml per tutte le stagioni
Di seguito riporto un esempio di file build.xml utilizzato in alcuni dei progetti JavaEE sui quali ho lavorato: non ho la pretesa di fornire la risposta a tutte le esigenze di ogni programmatore che leggerà questo articolo, ma piuttosto fornire una base (già piuttosto completa a dire il vero) dalla quale partire per la realizzazione di build file personalizzati più potenti e specifici. I target contenuti nel file permettono la compilazione, il build globale e il deploy di una applicazione EJB, e forniscono anche la possibilità di eseguire le invocazioni di sessions ed entities da parte di client standalone. Per prima cosa vediamo pezzo per pezzo i passaggi più importanti del file. In allegato il lettore potrà trovare il file completo.
Inizializzazione: definizione delle variabili di lavoro
Il file come al solito inizia con l'intestazione XML e il nome del progetto Ant
<?xml version="1.0" encoding="UTF-8" ?> <project name="MyAntProject" default="all" basedir="../.">
dove viene definito il target di default e la directory radice del progetto: definire questa posizione è utile perché permette di semplificare e in qualche modo automatizzare il processo di composizione delle varie variabili necessarie per le operazioni di compilazione, build, deploy ed esecuzione finale. A partire dalla directory base di progetto, come si puà notare, si possono comporre tutte le localizzazioni delle altre directory:
<target name="init" >
<!-- definizione delle varie directory di progetto -->
<property name="src.dir" value="${basedir}/src"/>
<property name="classes.dir" value="${basedir}/classes"/>
<property name="resources.dir" value="${basedir}/resources"/>
<property name="releases.dir" value="${basedir}/releases"/>
<property name="temp.dir" value="${basedir}/temp"/>
<property name="lib.dir" value="${basedir}/lib"/>
Le definizioni appena viste mappano quindi una struttura di directory di progetto ben precisa, che in questo caso è organizzata in questo modo:
- directoryradiceprogetto
- src: la directory con i sorgenti
- classes: la directory con i compilati
- resources: la directory contenente risorse di vario tipo da cui accingere per compilare
- releases: la directory con i build di progetto
- temp: una directory di appoggio utile per le fasi intermedi di produzione
- lib: una directory dove sono posizionate tutte le librerie di progetto necessarie per le fasi di compilazione e build
Parlando di configurazione di lavoro utile provvedere anche a impostare i vari parametri relativi alla connessione JDBC verso il database utilizzato nel progetto. Questa sezione è utile per automatizzare la creazione e popolamento del db utilizzato dalla applicazione (si veda il target successivo "create-db"). Di seguito è riportato il blocco con la configurazione per la connessione JDBC:
<!-- Proprietà di connessione JDBC --> <property name="db.driver.jar" value="mysql-connector-java-3.1.12-bin.jar"/> <property name="db.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="db.jdbc.url" value="jdbc:mysql://localhost:3306/NOMEDB"/> <property name="db.userid" value="NOMEUTENTE"/> <property name="db.password" value="PASSWORD"/> <property name="db.schema" value=""/> <!-- opzionalmente si può definire il dialetto utilizzato per le personalizzazioni delle varie istruzioni SQL --> <property name="hb.dialect" value="org.hibernate.dialect.MySQLDialect"/> <!-- si definiscono i nomi degli script SQL con i quali sarà gestito il db (creazione, popolamento, cancellazione) --> <property name="sql.dropDb" value="dropDb.sql"/> <property name="sql.createDb" value="createCBeanDbTable.sql"/> <!-- il nome del file DS utilizzato da JBoss per connettersi al db: si veda il target deploy-ds --> <property name="ds.name" value="DATASOURCE-FILENAME.xml"/>
Si noti il riferimento al driver JDBC che deve essere inserito nel classpath del runtime Ant (in questo caso il driver verrà inserito nella directory lib utilizzata per comporre il classpath). Per quanto concerne il deploy in ambito enterprise, può essere utile definire il nome dell'archivio che verrà creato durante la fase di build: in questo caso si è deciso quindi di definire una variabile apposita
<property name="archive.name" value="ejbapplication" />
Volendo deployare in un application server come JBoss o Tomcat, dove il deploy può essere fatto con una semplice operazione di copia, è necessario definire dove copiare i file di build; questa cosa si può ottenere banalmente definendo in una variabile il path assoluto, come ad esempio:
<property name="jboss.dist" value="/Applications/programming/java/jboss-4.0.4.GA"/>
Una alternativa più elegante (ma che richiede una opportuna configurazione della shell di esecuzione), prevede l'utilizzo della proprietà "env" che è associata al set di variabili di ambiente:
<property environment="env" />
<property name="jboss.dist" value="${env.JBOSS_HOME}"/>
</target>
Setup dell'ambiente
Spesso possono risultare drammaticamente utili alcuni task che effettuano alcune operazioni di preconfigurazione dell'ambiente di lavoro, specialmente in quei casi in cui si lavora in un team e si condivide il progetto su un repository CVS: alla prima operazione di checkout fra i vari file il nuovo membro del team si troverà anche tutte le risorse necessarie per configurare la propria piattaforma di lavoro (librerie, ma anche file di configurazione dell'application server, script di creazione SQL, etc..).
Molto comodo in questo contesto è il seguente target che si connette al database e tramite uno script SQL, recuperato dalla directory di risorse (${basedir}/sql), esegue una inizializzazione delle tabelle.
Di fatto non ci sono limiti alle operazioni che si possono eseguire sul database con questa tecnica dato che nel file SQL è possibile inserire un numero arbitrario di operazioni (di creazione, di popolamento, cancellazione).
<!-- crea le tabelle del database -->
<target name="create-db" depends="init">
<echo message="Creazione delle tabelle nel db"/>
<echo message="ATTENZIONE: db deve essere presente con nome come specificato nel JDBC URL"/>
<sql driver="${db.jdbc.driver}"
url="${db.jdbc.url}"
userid="${db.userid}"
password="${db.password}"
src="${basedir}/sql/${sql.createDB}">
<classpath>
<pathelement location="${basedir}/lib/${db.driver.jar}"/>
</classpath>
</sql>
</target>
Accanto alle operazioni di creazioni delle tabelle nel database, spesso è necessario configurare l'application server per consentire la connessione al database. In JBoss questa operazione è molto semplice e corrisponde a copiare un file XML (il datasource file) nella directory di deploy.
Anche se per la sua semplicità questa operazione potrebbe essere eseguita manualmente, per evitare dimenticanze (specie nei nuovi membri del team appena arrivati), ho introdotto nel mio file di build questo semplice target:
<target name="deploy-ds" depends="init">
<echo message="deploing the ds configuration file"/>
<copy file="${resources.dir}/${ds.name}" todir="${jboss.dist}/server/default/deploy" />
</target>
Ovviamente una organizzazione comoda potrebbe essere quella di riunire tutti i task di setup in modo piuttosto semplice, tramite la composizione delle dipendenze:
<target name="setup" depends="deploy-ds,create-db"> <echo message="Setup....."/> </target>
Compilazione
Un target classico e praticamente omnipresente: quello della compilazione. Molto utile il riferimento alla dir client di JBoss che consente di risolvere ogni riferimento a API e librerie di EJB ma non solo; chiunque abbia in qualche modo sviluppato applicazioni EJB, sa che la risoluzione delle varie librerie client EJB-JavaEE non è mai una cosa semplice, e quindi apprezzerà certamente la soluzione adottata, efficace quanto semplice.
<!-- Compila le classi di progetto -->
<target name="compile" depends="init">
<echo message="Compilazione.."/>
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}"
destdir="${classes.dir}"
debug="on"
deprecation="on"
optimize="off"
>
<classpath >
<fileset dir="${jboss.dist}/client">
<include name="**/*.jar"/>
</fileset>
</classpath>
<include name="**/*.java" />
</javac>
</target>
Creazione dell'archivio jar e deploy
Questo target crea il file jar di deploy (quindi in questo caso si fa esplicito riferimento ad una applicazione EJB) tramite il quale è possibile eseguire poi il deploy della applicazione. Si noti come sia possibile, anche se non necessario, eliminare i riferimenti alle classi che non appartengono alla parte server (sono le classi client di test):
<!-- Crea l'archivio the ejb-jar -->
<target name="make-ejb-jar" depends="compile,clean">
<echo message="Creating the ejb-jar archive"/>
<jar jarfile="${releases.dir}/${archive.name}.jar">
<fileset dir="${classes.dir}">
<exclude name="com.mokabyte.nomeprogetto.NomeClasse" />
</fileset>
<zipfileset dir="${resources.dir}" prefix="META-INF">
<include name="ejb-jar.xml" />
<include name="jboss.xml" />
<include name="jaws.xml" />
<include name="jbosscmp-jdbc.xml" />
</zipfileset>
</jar>
</target>
Si noti che questo target porta alla creazione dell'archivio utilizzando come nome quanto definito nella variabile ${archive.name}. Si noti anche la presenza del sotto-tag zipfileset che consente di creare un set di risorse da inserire nell'archivio: in questo caso si tratta dei vari deploy descpritor file (in formato XML), che verranno inseriti, come da specifica, nell'archivio jar nella sotto dir META-INF.
Infine il seguente target permette di eseguire il deploy della applicazione in JBoss della applicazione (in questo caso il deploy si riduce ad una semplice operazione di copia nella cartella di deploy dell'application server):
<!-- Deploy dell'archivio ejb-jar nella directory di deploy di JBoss -->
<target name="deploy-ejb-jar" depends="make-ejb-jar">
<echo message="deploing the ejb-jar archive"/>
<copy file="${releases.dir}/${archive.name}.jar" todir="${jboss.dist}/server/default/deploy" />
</target>
Esecuzione del client EJB
Una operazione banale ma spesso piuttosto macchinosa è quella che prepara la configurazione dell'ambiente di esecuzione del client per la connessione a componenti EJB remoti. In questo caso la difficoltà (o meglio la scomodità) consiste nel comporre il classpath con tutte le librerie necessarie per risolvere le classi EJB (i packages javax.ejb.* ) e per la connessione al JNDI context. Anche in questo caso Ant, grazie all'utilizzo delle variabili definite nela target init, permette di risolvere brillantemente e con poco lavoro questo problema: di fatto prendendo come base la directory di installazione di JBoss, il classpath viene composto con quanto trovato nella directory JBOSS_HOME/client:
<!-- Manda in esecuzione il client EJB -->
<target name="TestStateless" depends="compile">
<java classname="com/mokabyte/ejbproject/client/TestClient.class" fork="yes">
<classpath>
<pathelement location="${classes.dir}" />
<pathelement location="${resources.dir}" />
<fileset dir="${jboss.dist}/client">
<include name="**/*.jar"/>
</fileset>
</classpath>
</java>
</target>
Infine non dimenticare di pulire
E' buona cosa non dimenticare di definire il target che pulisce la directory di build, eliminando l'archivio jar. Si potrebbe estendere con successo facendo anche eliminare il contenuto della directory di compilazione:
<target name="clean" depends="init">
<delete file="${releases.dir}/${archive.name}.jar"/>
</target>
Infine il target che non dovrebbe mancare mai: quello che fa tutto, ovviamente opportunamente configurato:
<!-- "all" default target --> <target name="all" depends="deploy-ejb-jar"> </target> </project>
Conclusione
Come si è visto con una saggia organizzazione delle directory di progetto parallelamente ad un accorta sezione di inizializzazione delle variabili di progetto, si può utilizzare Ant per eseguire in maniera estremamente semplice le comuni operazioni di sviluppo JavaEE (ma non solo).
Sebbene sia sempre stato un grande fan dei più moderni e avanzati IDE Java, ritengo che questo tool, corredato di un buon file di build, rappresenti un indispensabile strumento di lavoro per ogni programmatore: Ant non deve certo sostituire prodotti come JDeveloper, Studio Creator, Eclipse, Netbeans o JBuilder, ma deve invece essere usato congiuntamente a tali strumenti avanzati sia per semplificare il lavoro sia per ridurre la dipendenza da tali prodotti.
In allegato (nel menu in alto a sinistra) il lettore troverà un archivio con una serie di directory a formare la struttura di base del progetto come presentata in questo articolo. Si controlli il file build.xml nella directory ant ed eventualmente il createdb.sql nella directory sql.
