Level: Intermediate Dan Jemiolo (danjemiolo@us.ibm.com), Advisory Software Engineer, IBM
02 Oct 2007 One of the goals of Project Zero is to encourage scripting as the primary means of creating RESTful resources and reusable components. Zero supports the Groovy and PHP scripting languages by default, but if you look closely at its architecture, you'll see there's no reason it can't support others as well. The Ruby language (and its Web 2.0 platform, Ruby on Rails) has enjoyed enormous success in the last few years, and many Ruby developers make their living creating the kind of applications that Zero is built for. This article will show Ruby enthusiasts how to have their cake and eat it too by adding support for their favorite language to the Zero platform.
Editor's note: IBM® WebSphere® sMash and IBM WebSphere sMash Developer Edition are based on the highly acclaimed Project Zero incubator project. Project Zero is the development community for WebSphere sMash and will continue to offer developers a cost-free platform for developing applications with the latest builds, the latest features, and the support of the community.
Before you get started
This article assumes that you have downloaded Project Zero and either completed the introductory tutorial or written a simple application yourself. You should also be familiar with the Ruby programming language, but I assume that's what brought you here in the first place!
Introduction
Project Zero provides support for scripting in Groovy and PHP out-of-the-box,
making it easy to create RESTful resources and utility scripts without any of the
complexity or configuration that Java™ developers are so used to dealing with. But even though the Zero team has chosen to throw its support behind Groovy and PHP, that doesn't mean that other dynamic scripting languages aren't just as popular or useful; it would be nice if, say, Ruby or Python developers could be at home with Zero too, reusing their skill sets and taking advantage of Zero's innovation. This article shows Ruby enthusiasts how to add support for their favorite language to the Zero platform and explains how users of other languages could do the same.
 |
The Project Zero community
Take a stroll around the Project Zero Web site and see how Project Zero provides a powerful -- but radically simple -- development and execution platform for modern Web applications. |
|
Understanding Zero's approach to scripting languages
Groovy and PHP may be the primary scripting languages for the Zero platform, but
they are integrated into the Zero runtime (called Zero Core) in a very
generic way. The architecture of Zero Core is designed to allow for executing
disparate scripts and templates, and nothing in the programming model depends on
the aforementioned languages. This section explains how Zero Core provides support
for Groovy and PHP and how you can use these same mechanisms to add Ruby.
Zero Core and the interpreter API
The Zero Core API includes an interface named zero.core.interpreter.Interpreter that is central to its execution of user scripts. This interface is used by the HTTP request handler to invoke different types of scripts based on their file extensions. When a new HTTP request is received, the request handler puts all of the HTTP data into Zero's global context, uses the target script's file extension to find the right Interpreter object, and then delegates the script invocation to that object; the script that is executed can access the HTTP request data through the global context. The Interpreter class has one method, invoke(), which is given the name of the script to execute and not much more.
Groovy support is implemented in a class named zero.core.interpreter.GroovyInterpreter. Its invoke() method uses Groovy's compiler API to parse and execute scripts. The mapping of .groovy files to the Groovy interpreter is established in Zero Core's own zero.config file, as shown in Listing 1:
Listing 1. Configuration of Groovy interpreter for Zero Core
[/app/interpreters]
.groovy=zero.core.interpreter.GroovyInterpreter
|
As you can see, interpreters are configured in zero.config by mapping file extensions to interpreter classes. All you need to do to add support for Ruby is to write an interpreter class and then add an entry to zero.config that associates it with Ruby (.rb) files.
Strategy for creating a Ruby interpreter
You must write your Ruby interpreter class in Java code to integrate with Zero Core, which means you have two options for executing Ruby scripts: you can either use the JDK's Runtime.exec() API to run Ruby as a separate process or you can use JRuby, which is a Java-based implementation of Ruby, to process the scripts in the same process. The JRuby option is more attractive for three reasons:
- Creating a separate process would prevent you from sharing the global context data structure, which is an essential part of all Zero scripts.
- JRuby allows you to invoke Java APIs from your Ruby code, so all of the Zero Core APIs will be accessible.
- Adding JRuby to a Zero application is as simple as adding a few JAR files to its /lib directory -- no environment variables are necessary.
This article uses JRuby 1.0. (For a link to the JRuby 1.0 download page, check the Resources section.)
To create an interpreter class for Ruby, you need to use JRuby's org.jruby.Main class, which allows you to not only execute Ruby
scripts but also redirect standard I/O and handle exceptions that are raised
because of developer error. The next section shows how to take the data that is passed to Interpreter.invoke() and convert it into API calls on org.jruby.Main.
Implementing an interpreter with JRuby
Before you extend Zero Core with your JRuby-based interpreter, you first need a sample application to develop and test with. If you're following along with the article, create a new Zero application named zero.scripting on your machine; if you want to see the final results right now, the Resources section links to a .zip file with the completed project.
Once you create the the zero.scripting project, you need to add some .jar files
from the JRuby distribution to its /lib directory. Specifically, you need to copy
over the jruby.jar and asm-2.2.3.jar files. If you intend to edit the code for the
interpreter class in Eclipse, you also want to add these .jar files to your Eclipse project's classpath.
The JRubyInterpreter class
To create the JRubyInterpreter class, create a new
source code file with an empty class skeleton and add the Interpreter interface to the definition. You then need to add a method declaration for invoke() so that the class compiles. From there, it's a matter of passing the data given to invoke() over to org.jruby.Main in the right format. Listing 2 shows an implementation that performs the following tasks:
- Redirects JRuby's standard output to the current HTTP request's output stream.
This allows Ruby developers to send HTTP response data back to the client in the same way that Groovy developers do.
- Creates an array of arguments for the JRuby runtime. These arguments include the
name of the target script and are in the same format they would be in if you were invoking JRuby from the command line.
- Executes the script by calling
Main.run().
Listing 2. The JRubyInterpreter class
package zero.scripting.ruby;
import java.io.File;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import org.jruby.Main;
import org.jruby.RubyInstanceConfig;
import zero.core.context.GlobalContext;
import zero.core.events.HandlerInfo;
import zero.core.interpreter.Interpreter;
public class JRubyInterpreter implements Interpreter
{
public void invoke(HandlerInfo handlerInfo)
{
final PrintWriter writer = GlobalContext.get("/request/writer");
//
// JRuby's API uses the deprecated PrintStream class, so we
// have to wrap the response writer in a PrintStream
//
PrintStream stream = new PrintStream(new OutputStream(){
public void write(int b) {
writer.write(b);
}
});
//
// Map stdout/stderr to HTTP response writer
//
RubyInstanceConfig jrubyConfig = new RubyInstanceConfig();
jrubyConfig.setOutput(stream);
jrubyConfig.setError(stream);
File scriptFile = new File(handlerInfo.handler);
String[] jrubyArgs = new String[]{ scriptFile.getPath() };
//
// Execute Ruby script!
//
Main jruby = new Main(jrubyConfig);
jruby.run(jrubyArgs);
}
}
|
You can add this code to your application's /java directory and compile it using the zero make command. The resulting class files will be copied to the application's /classes directory and loaded at run time.
Updating the Zero configuration file
Your code is in place, but you still have one more step to complete: like the
Groovy interpreter, you must register your Ruby interpreter with Zero Core so that
it knows how to handle requests for .rb files. Adding the stanza from Listing 3 to
your application's /config/zero.config file makes
things official:
Listing 3. Registering your Ruby interpreter with Zero Core
[/app/interpreters]
.rb=zero.scripting.ruby.JRubyInterpreter
|
Testing Ruby scripts
Now that Ruby support is in place, let's test it out by adding a Ruby script to your application and invoking it from a Web browser. To illustrate the complete integration of Ruby with Zero, your script will use Zero's GlobalContext API to read request data and create a response. The Ruby code in Listing 4 shows the entire contents of the global context by serializing it within the HTML response. Notice that even though it is using Zero Core's Java APIs, the syntax is pure Ruby.
Listing 4. Ruby script for testing your code
require "java"
include_class "zero.core.context.GlobalContext"
include_class "zero.core.context.SimpleFormatter"
print "<p>The current contents of the global context are:<pre>"
formatter = SimpleFormatter.new
GlobalContext.dump formatter
print formatter
print "</pre></p>"
|
Add the code from Listing 4 to a file named test-ruby.rb in your application's /public directory. You should then start the Zero application with the zero run command and test things out by pointing your favorite Web browser to http://localhost:8080/test-ruby.rb. The result should be a simple HTML page that displays the contents of the global context. Figure 1 shows a screenshot of what the page looks like in Mozilla Firefox.
Figure 1. HTML page created by your Ruby script
Conclusion
Project Zero does not include support for Ruby by default, but that doesn't mean that Ruby programmers have to drop their favorite language to use this new platform. Developers who are experienced with Ruby on Rails and considering a move to Zero can ease that transition by using the techniques in this article to keep their Ruby skills relevant while still taking advantage of Zero's new paradigms. Ruby developers can use the code that accompanies this article and tweak it as they see fit.
Download | Description | Name | Size | Download method |
|---|
| Source code for this article | wa-pz-rscript.zip | 4KB | HTTP |
|---|
Resources Learn
-
Browse the developerWorks Web development zone to find tools, code, and resources to get you started developing Web 2.0 applications today.
-
The developerWorks Ajax resource center is packed with information for all skill levels to help you build Ajax into your applications and dramatically improve your user's Web experience.
Get products and technologies
-
Download Project Zero and start applying the best practices covered in this article.
-
Download JRuby 1.0 to build and run the code included with this article.
-
Download the sample project created in this article, including the JRuby integration code and test files.
Discuss
About the author  | 
|  | Dan Jemiolo is an Advisory Software Engineer on IBM's Project Zero team in Research Triangle Park, NC.
He is currently working on reusable components for the Zero platform and its service catalog. His previous
work includes the design and development of Apache Muse 2.0 and participation in OASIS Web services standards
bodies. Dan came to IBM three years ago after earning his Master of Science degree in Computer Science from
Rensselaer Polytechnic Institute. |
Rate this page
|