Home / Blog / Using Jwt (yes, it's a J) with Clojure

History always repeats itself and sometimes that's good. Here's a second chance to be part of it: tmdtc.com

Using Jwt (yes, it's a J) with Clojure

Introduction

Jwt is a "Java library for developing web applications" with a widget-centric API. Jwt is actually the result of a translation to Java of the original Wt C++ code, which was originally developed by Koen Deforche and both are now maintained by Emweb .

I've been intrigued by Wt since I saw an (was that 3 year ago?!) announcement on Ajaxian for its widget approach, but I never used it due to its C++ base. Not that C++ is bad, it certainly has its justification in the embedded space, but I got used to dynamic scripting languages.

When Koen told me some months ago a Java version was on its way, I immediately told him I was eager to test it. Not with Java the language, but on Java the platform with Clojure (which I'll cover in this post) and JRuby (maybe for a future post, but if you want to test Wt from Ruby, take a look at Richard Dale's WtRuby which uses the C++ Wt).

Clojure is a "dynamic programming language that targets the Java Virtual Machine" which is a dialect of Lisp sharing with it "the code-as-data philosophy and a powerful macro system". Clojure is a recent language with a vibrant and steadily growing community. Despite being so young, there's already a book covering Clojure by the Pragmatic Programers .

Setup

I'm using Sun's Java 6 on an Debian, and got code for Clojure, Clojure-contrib and Jwt from their respective repositories. If this is fine for Clojure and Clojure-contrib, which build flawlessly with ant giving you a usable jar file, it caused problems with Jwt as the git repo seems to be out of sync... One advice here: be sure to download the Jwt zip file (currently 2.99.3) to avoid headaches wondering why you get blank pages served!

First step

Jwt applications need to run in a servlet container, and Compojure, a Clojure web development framework provides jetty.clj module allowing to create a servlet holder in which you can add an instantiated Jwt servlet. (thanks to Adrian Cuthbertson for this info!).

So, the only thing I needed to find is how to instanciate a WtServlet. To find out I decided to translate in Clojure the HelloWorld found in Jwt's examples . My first attempt was some some of literal translation of the Java code which was not very "clojurish" nor working as I always got blank pages served. Eventually I got everything sorted out: the problem was because the git repo doesn't contain the latest release, and the code was improved by Chouser on irc to look like this:

(ns be.nsa.server
  (:use compojure))

(import '(eu.webtoolkit.jwt WApplication
                    WEnvironment WtServlet WText 
                    WPushButton WLineEdit WBreak))

(defn make-hello-app [env]
  (let [wapp (new WApplication env)
        root (.getRoot wapp)
        line-edit (WLineEdit.)
        result-text (WText. "")
       ]
    (.setTitle wapp "HelloWorld")
    (.addWidget root (WText. "Hello there! "))
    wapp))

(def my-servlet
  (proxy [WtServlet] []
    (createApplication [env]
      (make-hello-app env))))

(defserver my-server {:port 8080} "/*" my-servlet  )

After declaring our namespace and saying we use compojure, we import the classes needed from Jwt.

The code itself is not very complex: it creates an instance of a proxy class to WtServlet, of which we overwrite the method createApplication (which receives as argument a WEnvironment instance). This method simply return the WApplication we instanciate in make-hello-app.

Implementing listeners

The second step needed to get the working hello world application, is to add a text field and a button. This is straight-forward, but then we need to add a listener on the button, which is defined like this in Java:

b.clicked().addListener(this, new Signal1.Listener<WMouseEvent>() {
   public void trigger(WMouseEvent a1) {
     greet();
   }
});

The listener needs to implement the nested interface Signal1.Listener. To get access to it, you use the syntax Signal1$Listener, but it is accessible only after you have imported it! If you don't import Signal1$Listener, you'll have to reference it by its fully qualified name (even if you imported Signal1)! I got a useful help from Cark on IRC for this, thanks!

Once accessing the nested interface was sorted out, implementing the listener was easy:

(.. (WPushButton. "Greet me" root) clicked
    (addListener  
          wapp
          (proxy [Signal1$Listener] [] 
               (trigger [ mouse-event ] 
                 (.setText result-text (.getText line-edit)) ))))

we add a listener to the object returned by the button's clicked method. The first argument to addListener is the wapp, the second argument is the proxy class' instance of which we implement the trigger method. This code to create the widget seems to be screaming for macros, see below.

At that time I though I was done, as implementing the second listener is done by this code:

(.. line-edit enterPressed
    (addListener 
       wapp   
        (proxy [Signal$Listener] [] 
            (trigger [] 
             (.setText result-text (.getText line-edit))))))

But it didn't work :(

The addListener method could not be resolved. After looking at it for hours spread on multiple days with the help of clojure wizards on the #clojure IRC channel, cgrand finally identified the problem (update (20090901): this problem has been patched and will be gone in future Clojure releases): proxy objects for nested interfaces are cached and identified by the interface's name. Meaning that if you have multiple interfaces with the same name in different packages, you'll get in trouble. The solution is to call proxy with a dummy interface that we don't care about, but which will change the key used to stored it in the proxy cache... In this example I added the interface Runnable:

(.. line-edit enterPressed 
    (addListener 
       wapp  
        (proxy [Signal$Listener Runnable] [] 
           (trigger [] 
             (.setText result-text (.getText line-edit))))))

And that's it, the first Jwt app written with Clojure is now running :-D

The code (that you'll find at the bottom of this page) is to be place in the file be/nsa/server.clj under your classpath, and can be loaded in a repl with

(use 'be.nsa.server.clj)

You can then start and stop the server with:

(compojure.server.jetty/start my-server)
(compojure.server.jetty/stop my-server)

Macro

Here is a macro greatly simplifying the creation of listeners:

(defmacro create-listener [ args & body]
  (let [argsnum# (if (< 0 (count args)) (count args) "") ]
  `(proxy [ ~(symbol (str "Signal" argsnum# "$Listener")) ] []
          (trigger ~args ~@body))))

It's really heloing development in our case, as the interface implemented will be determined based on the number of arguments we want trigger to have. So this call

(create-listener [a1 a2] (do-stuff a1 a2))

will implemented the interface Signal2$Listener and this call

(create-listener [] (do-stuff))

will implement Signal$Listener.

This macro is not usable in this state though, due to the Clojure bug describe above. To work around that bug, it is possible to add an argument to the macro, so say which dummy interface we want to implement:

(defmacro create-listener [ interface args & body]
  (let [argsnum# (if (< 0 (count args)) (count args) "") ]
  `(proxy [ ~(symbol (str "Signal" argsnum# "$Listener")) ~interface ] []
       (trigger ~args ~@body))))

The code

This code is compatible with Clojure 1.0. See below for updates...

(ns be.nsa.server
  (:use compojure))

(set! *warn-on-reflection* true)

(defmacro create-listener [ interface args & body]
  (let [argsnum# (if (< 0 (count args)) (count args) "") ]
  `(proxy [ ~(symbol (str "Signal" argsnum# "$Listener")) ~@interface ] []
         (trigger ~args ~@body))))

(import '(eu.webtoolkit.jwt WObject WApplication
                  WEnvironment WtServlet WText WPushButton 
                  WLineEdit WBreak Signal1 Signal1$Listener 
                  Signal Signal$Listener EventSignal))

(defn make-hello-app [env]
  (let [wapp (new WApplication env)
        root (.getRoot wapp)
        line-edit (WLineEdit.)
        result-text (WText. "")
       ]
    (.setTitle wapp "HelloWorld")
    (.addWidget root (WText. "Your name, please ? "))
    (.. (WPushButton. "Greet me" root) clicked 
          (addListener  
             wapp
             ( create-listener [] [mouse-event] 
               (.setText result-text (.getText line-edit)))))
   (.. line-edit enterPressed 
           (addListener 
               wapp  
             (create-listener [Runnable] [] 
               (.setText result-text (.getText line-edit)))))
    (.addWidget  root (WBreak.))
    (.addWidget  root line-edit)
    (.addWidget  root (WBreak.))
    (.addWidget  root result-text)
    wapp))

(def my-servlet
  (proxy [WtServlet] []
    (createApplication [env]
      (make-hello-app env))))


(defserver my-server {:port 8080} "/*" my-servlet  )

Update

With the Clojure bug mentioned above gone, and removing the unecessary gensym as it is outside the escaped code, the macro can be written:

(defmacro create-listener [ args & body]
  (let [argsnum (if (< 0 (count args)) (count args) "") ]
  `(proxy [ ~(symbol (str "Signal" argsnum "$Listener")) ] [] (trigger ~args ~@body))))

Disponible pour intervention 

Si vous recherchez quelqu'un pour renforcer temporairement votre équipe ou si vous avez besoin d'aide pour que l'IT aide votre société, n'hésitez pas, contactez-moi !

Lun Mar Merc Jeu Ven Sam Dim
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31