Neo4j unmanaged extension in Clojure


I needed to create an Unmanaged Extension in Neo4j but the prospect of using Java gave me shudders.

I have been programming in Clojure for a little while now. Given that Clojure is a JVM language, I decided to try to write the extension in that. Neo4j uses the JAX-RS API for supporting user supplied code. Luckily O’Reilly’s excellent book on Clojure, Clojure Programming had an example of a JAX-RS class. I love it when a plan comes together.

I modified that code to get what I needed. I hit across two issues:

  • A nasty deftype namespace not loading bug in Clojure. It seems to be an old one. But found a work around on Stack Overflow.

  • Neo4j 2.0 documentation stated that a transaction is required only when the graph is being written to. Nope. I’ve highlighted it to them and as ever they’ve quickly fixed it. I was trying to get a node by its ID and I was getting a NotInTransactionException. Anyway its not needed for this exercise.

Here is my working code (I’ve tried to stay close to the Neo4j provided example; my only deviation being calling another function from the function which directly handles the GET:

(ns jaxtest.core
  (:import (java.nio.charset Charset)
           (javax.ws.rs Path PathParam Produces GET)
           (javax.ws.rs.core Context Response)))

(defn dummy [] "Hello World")

(definterface HelloWorld
  (hello [^org.neo4j.graphdb.GraphDatabaseService database ^long node-id]))

(deftype ^{Path "/helloworld"} HelloWorldResource []
  HelloWorld
  (^{GET true
     Produces ["text/plain"]
     Path "/{nodeId}"}
    hello
    [this ^{Context true} database ^{PathParam "nodeId"} node-id]
      (require 'jaxtest.core)
      (let  [result  (-> (str (dummy) ", nodeId=" node-id)
                         (.getBytes (Charset/forName "UTF-8")))]
        (-> (Response/ok)
            (.entity result)
            .build))))

So now I have Java, Scala (for Cypher) and Clojure (for my extension) all running in the same JVM.

JVM ftw! Woot!!