Monday, April 07, 2008

Tips for JRuby engine: Compilable and CompiledScript usage

This is the seventh post of "Tips for JRuby engine" series I've written in this blog and shows how to use Compilable interface and CompiledScript class defined in JSR 223 scripting API, and why they are useful. These two APIs are used when programmers want to just parse scripts without evaluating them. Once the script has been parsed, programmers can evaluate it continuously as many times as they need without parsing. This mechnism is useful especially for web applications because we don't want to parse scripts for every http request after debugging has completed. We want to just evaluate them to process each http request. When scripting and Servlet APIs are put together, we could receive benefit of Compilable and CompiledScript APIs fully.

Let's look at an example program:
package mountainash;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CompilableTestServlet extends HttpServlet {

private ScriptEngine engine;
CompiledScript parsedScript, parsedScript2;

@Override
public void init() {
try {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName("jruby");

ClassLoader loader = getClass().getClassLoader();
InputStream iStream = loader.getResourceAsStream("ruby/simple2.rb");
Reader reader = new InputStreamReader(iStream, "UTF-8");
parsedScript = ((Compilable) engine).compile(reader);

iStream = loader.getResourceAsStream("ruby/simple3.rb");
reader = new InputStreamReader(iStream, "UTF-8");
parsedScript2 = ((Compilable) engine).compile(reader);
} catch (ScriptException ex) {
Logger.getLogger(CompilableTestServlet.class.getName()).log(Level.SEVERE, null, ex);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(CompilableTestServlet.class.getName()).log(Level.SEVERE, null, ex);
}
}

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
SimpleScriptContext scriptContext = new SimpleScriptContext();
scriptContext.setWriter(out);

try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet CompilableTestServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h3>Servlet CompilableTestServlet at " + request.getContextPath() + "</h3>");
out.println("<pre>");
parsedScript.eval(scriptContext);
out.println("<br/>");
parsedScript2.eval(scriptContext);
out.println("</pre>");
out.println("</body>");
out.println("</html>");
} catch (ScriptException ex) {
Logger.getLogger(CompilableTestServlet.class.getName()).log(Level.SEVERE, null, ex);
} finally {
out.close();
}
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

@Override
public String getServletInfo() {
return "JRuby engine test: use Compilable interface";
}
}

--------------------

# simple2.rb

puts "Compilable interface test"
puts "できたかな?"

--------------------

# simple3.rb

def greetings(to)
puts "Good morning, #{to}!"
end

greetings("glassfish")
greetings("grasshopper")
greetings("みなさん")
This servlet parses two Ruby scripts in its init() method, then, evaluated in its processRequest() method. This means that two scripts are parsed only once when the servlet is loaded onto a servlet container and are just evaluated everytime http request occurs. Two APIs, Compilable and CompiledScript, would contribute to improve performance of web applications. The busier the web application is, the more we can expect performance improvement because processing time for parsing is eliminated.

To try this example, please see my former post to know bases.

Here's a web application directory hierarchy:

+ Mountainash
+ WEB-INF
+ src
+ mountainash
- SimpleServlet.java
- CompilableTestServlet.java
+ ruby
- simple.rb
- simple2.rb
- simple.rb
+ lib
- jruby.jar
- jruby-engine.jar
+ classes -+
+ mountainash | automatically created
- SimpleServlet.class | directories and files
- CompilableTestSErvlet.class | by IDE
+ ruby |
- simple.rb |
- simple2.rb |
- simple3.rb -+
When this servlet gets run successfully, we get the following output:
<html>
<head>
<title>Servlet CompilableTestServlet</title>
</head>
<body>
<h3>Servlet CompilableTestServlet at /Mountainash</h3>
<pre>
Compilable interface test
できたかな?
<br/>
Good morning, glassfish!
Good morning, grasshopper!
Good morning, みなさん!
</pre>
</body>
</html>

Well, the names, Compilable and CompiledScript, are confusing for JRuby users. JRuby has a feature to compile Ruby script to create Java bytecode. Perhaps, Parsable and ParsedScript would be more understandable names.

1 comment:

Diesel Engine Parts said...

Interesting stuff I must admit. But what I really want to know is how did you really compose all this coding?? Okay, once I get this down hopefully it will work for me because I tried a couple different ways and none of them seemed to work. No worries though, that's why Google is here! haha I can literally find anything to everything on the web nowadays but enough of that.. But then thing is, that whenever I try inserting something like this it say, "The project type is not supported by this installation" How can I solve this problem?