mardi 11 janvier 2011

02 - Hello World en clojure

Deuxième étape dans la réalisation du projet 'clojadventure'.

Objectif : mettre en ligne une application très simple en utilisant GAE (Google App Engine) et Clojure.

Spécifications :
  • afficher "HelloWorld" sur la page d'accès;
  • réécrire les fichiers Java en Clojure.
Prérequis :
  • avoir un compte google actif;
  • sdk Google Appengine installé. cf partie 01;
  • ant installé. cf partie 01.
Actions :

1- Téléchargement / installation de ClojureBox (Clojure version 1.2)

Clojure Box permet d'installer très facilement  sous Windows : Clojure, Emacs et swank. Swank permet la communication entre Emacs et Clojure.  Emacs sera plus qu'un éditeur de texte, il sera notre IDE.

2- Code source

L'objectif est de traduire le code java que nous avons utilisé la dernière fois en code Clojure. Code source de la Servlet (src/test/java/helloworld/MyServlet.java) en Java :

package test.java.helloworld;

import java.io.IOException;
import javax.servlet.http.*;

public class MyServlet extends HttpServlet
{
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
 throws IOException
    {
        resp.setContentType("text/plain");
        resp.getWriter().println("My Hello, world !");
    }
}

Voici la traduction en Clojure (src/test/clojure/helloworld/MyServlet.clj) :

(ns test.clojure.helloworld.MyServlet
  (:gen-class :extends javax.servlet.http.HttpServlet
              :main    false ))

(defn -doGet [_ req resp]
  (.setContentType resp "text/plain")
  (.println (.getWriter resp) "My Clojure/GAE Hello, world !") )

a- La directive ':gen-class' indique au compilateur de générer du code JVM. Ce code correspond à une classe java 'test.clojure.helloworld.MyServlet'. Celle-ci n'aura pas de méthode 'main' (':main false') et aura une méthode doGet à deux arguments.

b- La méthode 'doGet' sera déduite de la fonction à trois arguments '-doGet'. Le '-' signifie au compilateur d'en faire une méthode. Le premier argument représente l'objet appliqué à la méthode : un objet de type MyServlet. Ici, comme il n'est pas utilisé dans le corps de la fonction, il est dénommé '_'.

A noter que le type des arguments n'est pas spécifié ici, même si Clojure le permet (notation méta). Le type d'exception n'est pas spécifié non plus. Concernant la transformation java => clojure, je n'ai pas encore regardé ce qui peut/doit être spécifié ni comment. En effet, seuls les points d'entrée devront être codés en pseudo-java, tout le reste sera en Clojure.

3- Mise à jour du fichier web.xml et du fichier index.html

Nous voulons que ce soit le test2 de notre application et que l'utilisateur puisse choisir le test à lancer à partir de la page d'accueil.

Ajouts au fichier "WEB-INF/web.xml" :

<web-app>
...
  <servlet>
    <servlet-name>helloworld-clojure</servlet-name>
    <servlet-class>test.clojure.helloworld.MyServlet</servlet-class>
  </servlet>
...
  <servlet-mapping>
    <servlet-name>helloworld-clojure</servlet-name>
    <url-pattern>/test2</url-pattern>
  </servlet-mapping>
...
</web-app>

Modifications de la page d'accueil (html/index.html) :

<html>
  <body>
    <h1>Accès aux applications de tests !</h1>
    <ul>
      <li><a href="test1">Test Hello world en Java !</a>
      <li><a href="test2">Test Hello world en Clojure !</a>
    </ul>
  </body>
</html>

4- Compilation, fichier build.xml et fichiers .jar

Voici la liste des commandes "ant" que nous souhaitons utiliser :
  • ant copyjars : recopie les librairies java nécessaires (GAE  + clojure).
  • ant compile1 : compile les fichiers java
  • ant compile2 : compile les servlets clojure. recopie les fichiers .clj dans l'arborescence war.
  • ant static : copie les fichiers statiques (web-inf, html, css, xml, js, ...) dans le répertoire war.
  • ant build1 : met à jour de façon incrémentale l'application test1 dans le dossier "war". static,compile1.
  • ant build2 : met à jour de façon incrémentale l'application test2 dans le dossier "war". static,compile2.
  • ant launch : lance le serveur de développement appengine.
  • ant run1 : build1, launch.
  • ant run2 : build2, launch.
  • ant run : build1, build2, launch.
  • ant clean : supprime tous les fichiers / répertoires de war.
  • ant deploy : déploie l'application sur le cloud.
Il ne reste plus qu'à exprimer ceci en langage Ant :

<project>
    <property name="sdk.dir" location="D:/outils/appengine-java-sdk-1.4.0" />
    <property name="clojure.dir" location="C:/Program Files (x86)/Clojure Box" />
    <import file="${sdk.dir}/config/user/ant-macros.xml" />
    <path id="project.classpath">
       <pathelement path="war/WEB-INF/classes" />
       <fileset dir="war/WEB-INF/lib">
          <include name="**/*.jar" />
       </fileset>
       <fileset dir="${sdk.dir}/lib">
          <include name="shared/**/*.jar" />
       </fileset>
    </path>
    <!-- ********************** targets **************************** -->
    <target name="copyjars"
            description="Copies the JARs to the WAR.">
       <mkdir dir="war/WEB-INF/lib" />
       <copy todir="war/WEB-INF/lib"
             flatten="true">
          <fileset dir="${sdk.dir}/lib/user">
             <include name="**/*.jar" />
          </fileset>
       </copy>
       <copy todir="war/WEB-INF/lib"
             flatten="true">
          <fileset dir="${clojure.dir}/lib">
             <include name="*.jar" />
          </fileset>
       </copy>
    </target>
    <target name="compile1" depends="copyjars"
            description="Compiles Java source to the WAR.">
       <mkdir dir="war/WEB-INF/classes" />
       <javac
             srcdir="src/test/java/helloworld"
             destdir="war/WEB-INF/classes"
             classpathref="project.classpath"
             includeantruntime="false"
             debug="on" />
    </target>
    <target name="compile2" depends="copyjars"
            description="Compiles Clojure Servlet and copies .clj source files to the WAR.">
       <mkdir dir="war/WEB-INF/classes/test/clojure" />
       <copy todir="war/WEB-INF/classes/test/clojure">
          <fileset dir="src/test/clojure">
             <include name="**/*.clj" />
          </fileset>
       </copy>
       <java classname    = "clojure.lang.Compile"
             classpathref = "project.classpath"
             failonerror  = "true" >
          <classpath path="src" />
          <sysproperty key="clojure.compile.path" value="war/WEB-INF/classes"/>
          <arg value="test.clojure.helloworld.MyServlet" />
       </java>
    </target>
    <target name="static"
            description="Copies the static files to the WAR (deployement, html).">
       <mkdir dir="war/WEB-INF" />
       <copy todir="war/WEB-INF">
          <fileset dir="WEB-INF"/>
       </copy>
       <copy todir="war">
          <fileset dir="html"/>
       </copy>
    </target>

    <target name="build1" depends="static,compile1"
            description="test1 application update.">
    </target>

    <target name="build2" depends="static,compile2"
            description="test2 application update.">
    </target>

    <target name="launch"
            description="Starts the development server">
       <!-- dev_appserver2 = fixed dev_appserver (bug 2093: ctrl-c doesn't stop server)
            cf appengine...config/user/ant-macros.xml -->
       <dev_appserver2 war="war" address="0.0.0.0"/>
    </target>

    <target name="run1" depends="build1,launch"
            description="compile and start test1 application">
    </target>

    <target name="run2" depends="build2,launch"
            description="compile and start test2 application">
    </target>

    <target name="run" depends="build1,build2,launch"
            description="build and start test1/test2 application">
    </target>

    <target name="clean"
            description="delete all war files and directories" >
       <delete dir="war"/>
    </target>

    <target name="deploy"
            description="Deploy this application on the cloud." >
       <appcfg action="update" war="war"/>
    </target>

</project>

A noter la compilation Clojure de la servlet :

<target name="compile2" depends="copyjars"
        description="Compiles Clojure Servlet and copies .clj source files to the WAR.">
  ...
  <java classname    = "clojure.lang.Compile"
        classpathref = "project.classpath"
        failonerror  = "true" >
    <classpath path="src" />
    <sysproperty key="clojure.compile.path" value="war/WEB-INF/classes"/>
    <arg value="test.clojure.helloworld.MyServlet" />
  </java>
</target>

L'inner tag "classpath" se concatène avec le classpathref. Vu que les fichiers .clj sont recopiés sous "war/web-inf/classes", je pense que cet ajout est superflu, à tester ... La propriété système "clojure.compile.path" indique à Clojure où celui-ci doit créer les fichiers compilés. La commande "ant run" doit maintenant compiler test1 et test2 puis lancer le serveur de test local.


Menu des tests


HelloWorld à partir d'une servlet écrite en Clojure !


La prochaine fois on essaiera de réaliser l'ensemble du tutoriel GAE en Clojure.

Aucun commentaire:

Enregistrer un commentaire