Learning Clojure and JAX-WS at once – Clojure supports annotations, but JAX-WS doesn’t like it

While reading the redbook IBM WebSphere Application Server V7.0 Web Services Guide, I’ve stumbled upon JAX-WS javax.xml.ws.Endpoint class. It’s been a while since I worked with Clojure and since one of the selling points of dynamic languages like Clojure is their ability to boost prototyping to the maximum, a weird idea crossed my mind – give it a try in…Clojure REPL. Learning more – Clojure and JAX-WS API – is usually better than learning less, especially when more means simpler.

The javax.xml.ws.Endpoint class lets you publish a web service with no hassle – call its publish method and you should be done.

While publishing a web service in Clojure might seem to be an easy affair, the development might possibly not. In the bottom-up approach where one starts with a implementation class to generate WSDL, in Java it’s based upon the JAX-WS annotations, mostly @WebService.

A bit of Googling and it turned out that Clojure supports annotations – Annotations.

With a very helpful reading of Building EJBs with Clojure, it should be easy, shouldn’t it? Not that much. Experience it yourself while following my steps in Clojure REPL.

Just to make sure we’re on the same track – I’m running the tests with Clojure 1.3.0-alpha4.

$ clj
CLOJURE_DIR:  /Users/jacek/apps/clojure
CLOJURE_CONTRIB_JAR:  /Users/jacek/apps/clojure-contrib-1.3.0-alpha4.jar
Clojure 1.3.0-alpha4

I’m going to create a single interface with deftype, create an instance with proxy and boot up the Endpoint with the publish(String address, Object implementor) method.

user=> (deftype #^{WebService} ClojureWS [] (#^String sayHello [this s] (str "Hello" s)))
ArrayIndexOutOfBoundsException 1  clojure.lang.PersistentArrayMap$Seq.first (PersistentArrayMap.java:234)
[]
CompilerException java.lang.Exception: Unable to resolve symbol: sayHello in this context, compiling:(NO_SOURCE_PATH:1) 
Exception Unmatched delimiter: )  clojure.lang.LispReader$UnmatchedDelimiterReader.invoke (LispReader.java:1039)

;; I didn't know the exact deftype and the annotation syntax
;; and missed the brackets after WebService and the interface ClojureWS implements

user=> (deftype #^{WebService {}} ClojureWS [] (#^String sayHello [this s] (str "Hello" s)))
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.Symbol  clojure.core/ns-resolve (core.clj:3802)

;; It's much better now - the error is that the interface ClojureWS should implement is not specified.
;; The annotation - WebService - should be resolvable, i.e. imported as any other Java class

user=> (import [javax.jws WebService])
javax.jws.WebService

;; Before I realized I do need an interface I figured out no interface is acceptable

user=> (deftype #^{WebService {}} ClojureWS [])
user.ClojureWS

;; Find out whether ClojureWS is indeed annotated

user=> (.getAnnotations ClojureWS)
#<Annotation[] [Ljava.lang.annotation.Annotation;@1cad157f>
user=> (seq (.getAnnotations ClojureWS))
(#<$Proxy3 @javax.jws.WebService(wsdlLocation=, targetNamespace=, name=, endpointInterface=, portName=, serviceName=)>)

;; On to JAX-WS...

user=> (import [javax.xml.ws Endpoint])
javax.xml.ws.Endpoint

user=> (def clj-ws (ClojureWS.))
#'user/clj-ws

user=> (Endpoint/create clj-ws)
#<EndpointImpl com.sun.xml.internal.ws.transport.http.server.EndpointImpl@5e89c116>

user=> (.publish (Endpoint/create clj-ws) "http://localhost:8888/ClojureWS")
RuntimeModelerException A @WebService.targetNamespace must be specified on classes with no package.  Class: user.ClojureWS  com.sun.xml.internal.ws.model.RuntimeModeler.getServiceName (RuntimeModeler.java:1315)

;; How came there was no package when it was printed out - user.ClojureWS?!
;; I guess that the JAX-WS implementation is not prepared for such highly-dynamic environment.

;; Specyfing the attribute targetNamespace of WebService is to create a map

user=> (deftype #^{WebService {:targetNamespace "http://japila.pl"}} ClojureWS [])
user.ClojureWS

user=> (def clj-ws (ClojureWS.))
#'user/clj-ws

user=> (.publish (Endpoint/create clj-ws) "http://localhost:8888/ClojureWS")
RuntimeModelerException The web service defined by the class user.ClojureWS does not contain any valid WebMethods.  com.sun.xml.internal.ws.model.RuntimeModeler.buildRuntimeModel (RuntimeModeler.java:242)

;; Huh?! I shall have to specify a superinterface for ClojureWS.

user=> (deftype #^{WebService {:targetNamespace "http://japila.pl"}} ClojureWS [] Object (sayHello [this s] (str "Hello," s)))
CompilerException java.lang.IllegalArgumentException: Can't define method not in interfaces: sayHello, compiling:(NO_SOURCE_PATH:23) 

;; I should've known it - Object doesn't provide sayHello, does it?
;; Hey, what about toString - it's public and available.

user=> (deftype #^{WebService {:targetNamespace "http://japila.pl"}} ClojureWS [] Object (toString [this s] (str "Hello," s)))
CompilerException java.lang.IllegalArgumentException: Can't define method not in interfaces: toString, compiling:(NO_SOURCE_PATH:24) 

;; It looks like Object.toString is not enough - perhaps no types are the culprit. Never mind...

;; Let's create a contract - an interface for the web service

user=> (definterface Hello (sayHello [s]))
user.Hello

user=> (deftype #^{WebService {:targetNamespace "http://japila.pl"}} ClojureWS [] Hello (sayHello [this s] (str "Hello," s)))
user.ClojureWS

user=> (def clj-ws (ClojureWS.))
#'user/clj-ws

user=> (.publish (Endpoint/create clj-ws) "http://localhost:8888/ClojureWS")
Feb 19, 2011 5:47:19 PM com.sun.xml.internal.ws.model.RuntimeModeler getRequestWrapperClass
INFO: Dynamically creating request wrapper Class jaxws.SayHello
Feb 19, 2011 5:47:19 PM com.sun.xml.internal.ws.model.RuntimeModeler getResponseWrapperClass
INFO: Dynamically creating response wrapper bean Class jaxws.SayHelloResponse
nil

It works! Wait. Does it really work? Let’s open a browser and point it to the http://localhost:8888/ClojureWS address.

I don’t really know whether I should be concerned with the response – No JAX-WS context information available and yet I could find no explanation on the Internet.

The http://localhost:8888/ClojureWS?wsdl worked fine – the WSDL is displayed in the browser.

When I tried to access the web service with a client – the Web Services Explorer in Eclipse – the response had no input parameter.

To recap the issues I run into:

  1. The namespace of a deftype’d class is not recognized by JAX-WS and targetNamespace is required.
  2. What does the message No JAX-WS context information available mean?
  3. Why does the output of the ClojureWS not contain the input parameter?

Any help would be greatly appreciated.

Be Sociable, Share!
This entry was posted in Java EE, Languages.

8 Responses to Learning Clojure and JAX-WS at once – Clojure supports annotations, but JAX-WS doesn’t like it

  1. Shree Mulay says:

    I’m new to Clojure as a whole. Just a thought, what about trying clojure-1.2.0 and clojure-contrib-1.2.0 instead of the bleeding edge stuff? On some other examples I’ve been trying out, things don’t seem to work until I use the established code bases. Perhaps this may make a difference. Wouldn’t hurt to try, eh…

    • Jacek Laskowski says:

      A neat idea, but while learning a new language I’d love to be on a cutting edge version so my learning is up-to-date. Even tough problems in an implementation may yield false beliefs, in the end they’re mostly harmless and being able to fix or work around some gives more fun.

  2. jbs says:

    It works. [jdk1.7.0_10 and clojure 1.4.0 and classpath appropriateness etc.]

    Just publish the endpoint:

    (defprotocol Calculator (add [this a b]))

    (deftype ^{WebService {:targetNamespace “test.seltzer”}}
    CalcWeb []
    Calculator (^{WebMethod []} add [this a b] (+ a b)))

    (def endpoint (Endpoint/publish “http://localhost:8080/calcWeb” (CalcWeb.)))

    Then create the client classes:

    wsimport -d classes http://localhost:8080/calcWeb?wsdl

    Finally run client:

    (let [calcWeb-service (seltzer.test.CalcWebService. )
    calcWeb-proxy (.getCalcWebPort calcWeb-service)
    result (.add calcWeb-proxy 2 3)]
    (println “result: ” result)
    (println “again result: ” (.add calcWeb-proxy 4 7)))

  3. jar says:

    jbs sums it up very nicely. Thanks to both of you for the inspiration!

Leave a Reply

%d bloggers like this: