Oddthesis-logo

JBoss WebServices and Ruby

Given thesis 3, "JBoss can provide enterprise services to Rails apps" and my need to provide an secured EC2 SOAP API to emulate Amazon locally, I figured it was time to take a look at JBossWS

JBossWS can use JBoss's own stack (jbossws-native), Apache-CXF or Sun's Metro project to drive the underlying SOAP stuff.  For ease, I've just stuck with the jbossws-native for now.

Implementing a JAX-WS handler with Ruby

Normally, JAX-WS likes annotations.  A lot.  It also seems to like code generation, if you use something like wsprovide or wsdl2java.  You end up with a Java method for each operation, and annotations to link it all together. 

That's just not going to work for the dynamic world of Ruby.

Thankfully, they do provide the javax.xml.ws.Provider interface.  Implement that, and now your class only needs a single method to handle all operations:

public Source invoke(Source request) {
...
return response;
}

This is more akin to the service(...) method on servlets.  In my case, it'll route all handling through a JRuby interpreter to classes inside your Rails app.

Since I'm working from an existing WSDL, I need to link up the definitions in the WSDL to the Java bits that will handle them.  I have to tell the container that some Java class will handle the service defined in the WSDL.

So we're back to annotations.  Seems like we can't get away from the compiler.  But we can get away from compile-time compiling.  Stefano Maestri suggested using Javassist, which enables us to deal with run-time compilation, instead.

Code generation

So it's fairly easy to put the bulk of the dispatch-to-ruby logic into a real Java class, and generate a thin shim at runtime to hold the @WebServiceProvider annotation with the wiring information needed.

While we're there, we also generate a constructor that sets the name of the ruby service, along with the name of the JRuby runtime-factory deployed by another part of the process.  The handler can use the JRuby factory and the class name to instantiate your Ruby class and dispatch requests to it.

That's awesome.  But what triggers all of this?

Deploying Ruby JAX-WS handlers

We've talked about deployers before.  And we're going to do it again.

If you recall all the previous deployment discussions, when a Rails app is deployed, it throws off a RailsApplicationMetaData object early on.  This signals that we've got a Rails app being deployed, and tells us useful things like the RAILS_ROOT and RAILS_ENV.

To deploy web-services, we add in another deployer or two. First, we have a RailsWebServicesDeployer, which knows just enough to scan your Rails application and find things to handle webservices.  It creates a generic (not-Rails-specific) RubyWebServicesMetaData, describing the Ruby class to handle the service.

This all happens around the time of the PARSE stage of deployment.

After the CLASSLOADER stage is complete, so we can safely generate classes into the app's classloader, we fire off the Javassist generate based upon the RubyWebServicesMetaData we find.

Once we've added our annotated shim classes to the classloader, we look for a JBossWebMetaData, which you've probably worked with in its jboss-web.xml form.  We adjust it so that our generated classes are listed as servlets, we set up the mappings and URL patterns, and then let it slide out of our hands.

Ruby WS deployers

Lastly, the JBossWS deployers kick in, and do whatever it is they do to turn a JBossWebMetaData into deployed web-services.  We don't have to care about that bit at all.

Bottom Line

In general, I think the strategy for deploying Rails-centric bits of JBoss enterprise services will involve a handful of deployers wedged in at the appropriate stages, and the ability to poke around in Ruby classes to generate annotations on Java subclasses.  Sounds complex, but it's not, really.

Stuff like this:

class MyRubyHandler < JBossRubyHandler
targetNamespace 'http://jboss.org/myservice'
end

Becomes stuff like this:

// Generated
@WebServiceProvider(
targetNamespace="http://jboss.org/myservice"
)
public class MyRubyHandlerProxy extends BaseRubyHandlerProxy {
public MyRubyHandlerProxy() {
super( "MyRubyHandler", "factory.MyAppRubyFactory" );
}
}

Given Javassist and a strong confidence in writing deployers, we can avoid any explicit user-invoked compilation phase, while still wiring up to Java technologies that insist on annotations.

Comments

Adam Warski, 08:59am UTC, 06 February 2009

Nice hacking! This really has potential I think ... you're now doing java meta-programming in ruby! :)

Especially that the annotations are usually covered by standards, so generating them is "safe" (meaning that they very probably won't change over time, when a new version of an implementation is released) and you in fact support any implementation of the standard.

You could imagine generating JPA mappings based on inspection or output of a small Ruby (or Scala, Groovy ...) program.

Login to avoid moderation.

Sign up if you need an account.

Creative Commons License Copyright 2008. Odd Thesis by Bob McWhirter is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.