Starting on version 1.13 Mentawai fully supports JRuby. You are able to write your actions in Ruby (or your whole application) or you can call any Ruby code from your Mentawai web application.
To support plain Ruby, all you need to do is copy the jruby.jar file inside the /WEB-INF/lib directory of your Mentawai web application. However, if you plan to use RubyGems and the Ruby Native Libraries (like 'date' for example) you will need to install JRuby in the machine running your Tomcat web server. To install JRuby is very easy. Follow the steps below:
Download the zip file with the JRuby installation from here: http://dist.codehaus.org/jruby/ (Ex: jruby-bin-1.1.1.zip)
Extract everything inside your root directory (or any directory)
Set the environment variable JRUBY_HOME. (Ex: JRUBY_HOME=c:\jruby-1.1.1)
Set the environment variable JRUBY_OPTS to -rubygems so that you don't need to require 'rubygems' in your code.
Change your PATH environment variable to include %JRUBY_HOME\bin so that you can call the jruby script from anywhere in your shell.
If you did the above steps correctly, you should be able to play with jruby in your shell. Check the examplew below:
c:\>jruby -v ruby 1.8.6 (2008-04-22 rev 6555) [x86-jruby1.1.1] c:\>jruby -S gem -v 1.1.0 c:\>jruby -S gem list --local *** LOCAL GEMS *** hoe (1.5.1) hpricot (0.6) jruby-openssl (0.2.3) mechanize (0.7.6) rake (0.8.1) rspec (1.1.3) rubyforge (0.4.5) sources (0.0.1)
Now let's check an example of how to run Ruby inside your Mentawai web application. (download Mentawai 1.13 here if you need to)
All your ruby code must be placed inside the /WEB-INF/ruby/ directory of your web application. Mentawai loads (do the require) any Ruby file from this directory plus it will reload any modified file pretty much like a JSP.
An action written in Ruby: /WEB-INF/ruby/action_test.rb
include_class 'org.mentawai.core.BaseAction' class MyFirstRubyAction < BaseAction def execute output["msg"] = "Hello from JRuby !!!!" :success end end
A Java Action calling Ruby code: /WEB-INF/src/org/hello/action/Hello.java
package org.hello.action; import java.util.Iterator; import java.util.Map; import org.mentawai.core.BaseAction; import org.mentawai.jruby.JRubyInterpreter; public class Hello extends BaseAction { JRubyInterpreter ruby = JRubyInterpreter.getInstance(); public String execute() { Object users = ruby.eval("Users.new"); Map map = (Map) ruby.call(users, "get_users"); Iterator iter = map.keySet().iterator(); StringBuilder sb = new StringBuilder(); while(iter.hasNext()) { sb.append(iter.next()).append(" "); } output.setValue("msg", "Hello my friends: " + sb.toString()); return SUCCESS; } }
Ruby code which is called by the Java action above: /WEB-INF/ruby/users_test.rb
class Users def get_users a = {} a['Sergio'] = 1 a['Robert'] = 2 a end end
ApplicationManager:
package org.hello; import org.hello.action.Hello; public class ApplicationManager extends org.mentawai.core.ApplicationManager { @Override public void loadActions() { action("/HelloRuby1", Hello.class).on(SUCCESS, "/hello.jsp"); ruby("/HelloRuby2", "MyFirstRubyAction").on(SUCCESS, "/hello.jsp"); } }
Below we have a simple tutorial for the features of the Mentawai integration with JRuby
Simple API to call ruby methods on any Ruby object from Java
JRubyWrapper for making ruby method calls even easier
Support method chaining. Ex: "methods.sort.grep"
Auto-reload any ruby file from the loadpath
Load any ruby file from the classpath with the loadFromClasspath method
Get a singleton instance of the JRubyInterpreter anywhere in your code with the JRubyInterpreter.getInstance() method
Support all JRuby environment variables and provide defaults for them. Ex: JRUBY_OPTS for -rubygems and -Ku , JRUBY_SHELL, JRUBY_SCRIPT, JRUBY_LIB and JRUBY_HOME.
Windows and Linux support
The file foo.rb anywhere in your loadpath (Ruby equivalent of Java's classpath)
OBS: Don't forget to change this file to see how it will be automatically reloaded by JRubyInterpreter.
class Foo def initialize(name) @name = name end attr_accessor :name def hi puts "Hi from #{name}" end def get_a_list [1,2,3] end def get_a_hash {"one" => 1, "two" => 2} end def to_s "I am #{name}" end end
Plug and play with JRubyInterpreter:
import org.mentawai.jruby.*; import java.util.List; import java.util.Map; public class Test { public static void main(String[] args) throws Exception { JRubyInterpreter ruby = JRubyInterpreter.getInstance(); Object foo = ruby.eval("Foo.new('Sergio')"); // using eval... System.out.println("Foo class: " + foo.getClass()); // should be a RubyObject... System.out.println("Foo: " + foo); // to_s from Ruby will be called! ruby.call(foo, "hi"); // calling method hi... ruby.call(foo, "name=", "Joe"); // calling setter... ruby.call(foo, "hi"); // calling method hi... ruby.set(foo, "name", "Bill"); // shorter version for calling setter... ruby.call(foo, "hi"); // calling method hi... System.out.println("Name: " + ruby.call(foo, "name")); // calling getter... List<String> methods = (List<String>) ruby.call(foo, "methods.sort"); // method chaining... for(String s: methods) { System.out.println("M1: " + s); } methods = (List<String>) ruby.call(foo, "methods.sort.grep", "name="); // method chaining with params... (params only for last method!) for(String s: methods) { System.out.println("M2: " + s); } // testing respond_to? boolean ok = ruby.respondTo(foo, "get_a_list"); // calling Ruby's respond_to?... System.out.println("get_a_list: " + (ok ? "OK" : "NOTOK")); // getting a list... List<Object> list = (List<Object>) ruby.call(foo, "get_a_list"); for(Object obj: list) { System.out.println(obj + " / " + obj.getClass()); } // getting a hash... Map<Object, Object> map = (Map<Object, Object>) ruby.call(foo, "get_a_hash"); for(Object key: map.keySet()) { Object v = map.get(key); System.out.println(key + " / " + key.getClass() + " => " + v + " / " + v.getClass()); } } }
Using the JRubyWrapper:
import org.mentawai.jruby.*; import java.util.List; import java.util.Map; public class TestWrapper { public static void main(String[] args) throws Exception { JRubyInterpreter ruby = JRubyInterpreter.getInstance(); Object foo = ruby.eval("Foo.new('Sergio')"); // using eval... System.out.println("Foo class: " + foo.getClass()); // should be a RubyObject... System.out.println("Foo: " + foo); // to_s from Ruby will be called! JRubyWrapper fooW = new JRubyWrapper(foo); fooW.call("hi"); // calling method hi... fooW.call("name=", "Joe"); // calling setter... fooW.call("hi"); // calling method hi... fooW.set("name", "Bill"); // shorter version for calling setter... fooW.call("hi"); // calling method hi... System.out.println("Name: " + fooW.call("name")); // calling getter... boolean ok = fooW.respondTo("get_a_hash"); System.out.println("Ok: " + ok); } }
Because Scala code is compiled into Java bytecode, you can code your web application using Scala instead of Java.
Below is an example of a Mentawai action coded in Scala:
package org.mentablank.action import org.mentawai.core.BaseAction import org.mentawai.core.Action._ class HelloScala extends BaseAction { var msg:String = null def hello = { if (msg == null) msg = "NULL" output.setValue("hi", "Hello from Menta: " + msg) SUCCESS } }
Compile this Scala class with the following command line:
scalac -classpath mentawai.jar;. org\mentablank\action\HelloScala.scala
This will create a file HelloScala.class inside the org\mentablank\action directory. Now you can use this class as any other Java class. Just remember to put the scala-library.jar inside the your WEB-INF/lib directory.
Let's use the javap program, that comes with Scala, to inspect our Scala class from the point-of-view of a Java program:
C:\java\scala>javap org.mentablank.action.HelloScala Compiled from "HelloScala.scala" public class org.mentablank.action.HelloScala extends org.mentawai.core.BaseAction implements scala.ScalaObject{ public org.mentablank.action.HelloScala(); public java.lang.String hello(); public void msg_$eq(java.lang.String); public java.lang.String msg(); public int $tag(); }
This will give you some hints about the Java class, but nothing can be better than a decompilation using JAD (Java Decompiler):
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: HelloScala.scala package org.mentablank.action; import org.mentawai.core.BaseAction; import org.mentawai.core.Output; import scala.ScalaObject; public class HelloScala extends BaseAction implements ScalaObject { public HelloScala() { Object _tmp = null; msg = null; super(); } public String hello() { String s; if((s = msg()) == null || s.equals(null)) msg_$eq("NULL"); super.output.setValue("hi", "Hello from Menta: " + msg()); return "success"; } public void msg_$eq(String x$1) { msg = x$1; } public String msg() { return msg; } public int $tag() { return scala.ScalaObject.class.$tag(this); } private String msg; }
Enjoy using Scala with Mentawai!
Some Scala resources:
http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-1
http://alblue.blogspot.com/2007/10/scala-introduction-to-scala-interpreter.html
http://www.ibm.com/developerworks/java/library/j-scala01228.html
Mentawai comes out-of-the-box with support for C3P0 and DBCP connection pools. All you need is two lines:
In the ApplicationManager.java:
public class ApplicationManager extends org.mentawai.core.ApplicationManager { private ConnectionHandler connHandler = null; public void init(Context application) { // assuming you have a mysql database with a "lohis" database with username "lohis" and password "lohis" this.connHandler = new DBCPConnectionHandler("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/lohis?autoReconnect=true", "lohis", "lohis"); } public void loadAction() { filter(new ConnectionFilter("conn", connHandler)); // ... } }
Then inside your actions you just do:
public class HelloMyPool extends BaseAction { public String execute() throws Exception { Connection conn = (Connection) input.getValue("conn"); // do what you have to do with the connection... return SUCCESS; } }
You don't even have to worry about returning the connection to the pool. The ConnectionFilter will make sure that happen for you.
You can also create a C3P0ConnectionHandler the same way to use the C3P0 connection pool instead. Just make sure the pool and the JDBC jars are in your WEB-INF/lib directory.
Mentawai uses JGroups (the best Java cluster solution?) to implement a distributed cache. This can be useful when you have many web servers in load balance and you want to accomplish seamless fail-over.
Cache cache = new DistributedCache("userCache", "user_channel", 1024 * 100); System.out.println("Cache size: " + cache.size()); // will be already in sync with the cluster... cache.put(user.getId(), user);
That's it! Just make sure the objects you place in the cache are serializable and all instances of your cache running in different machines will share the same data.
The default cache implementation is LRU (Least Resource Usage), so the least accessed entry will be removed when the cache gets full. You can also uso a FIFOCache (first in, first out, in other words, the oldest entry will be removed) like that:
Cache cache = new DistributedCache("userCache", "user_channel", 1024 * 100, FIFOCache.class); cache.put(user.getId(), user);
If you are bringing a new web server into the load balance pool, keep in mind that it will first have to get all the entries from the cluster before it can start operating. This can take some time if the cache is too big.
Another option is to use the SynchronizedCache which just synchronizes new entries with the other nodes and does not have the startup overhead.
Cache cache = new SynchronizedCache("userCache", "user_channel", 1024 * 100); System.out.println("Cache size: " + cache.size()); // will start at ZERO
Let's say for example that you want a tag that will print the number of friends of an user. If he has only one friend, the word "friend" will be in the singular form. If he has more than one friend, then the word is "friends". And if he has no friends at all, then you will print a friendly message saying nobody likes him (or just something like "You have no friends!").
public class NumberOfFriendsTag extends PrintTag { public String getStringToPrint() throws JspException { Output output = action.getOutput(); User user = (User) output.getValue("user"); int nFriends = user.getNumberOfFriends(); if (nFriends == 1) { return "You have 1 friend!"; } else if (nFriends == 0) { return "You have no friends at all! Don't code too much!"; } else { return "You have " + nFriends + " friends!"; } } }
Notice that because you extended from PrintTag, you have access to the following protected instance variables:
protected ServletContext application = null; protected HttpSession session = null; protected HttpServletRequest req = null; protected HttpServletResponse res = null; protected Action action = null; protected Locale loc = null;
Now to use your tag, you need a TLD file. This is a Servlet API requirement and there's nothing we can do about this file, so save the XML below in a file inside the /WEB-INF/tld directory, for example taglib.tld:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>myTags</short-name> <display-name>myTags</display-name> <tag> <name>showNumberOfFriends</name> <tag-class>com.mysite.tag.NumberOfFriendsTag</tag-class> <body-content>empty</body-content> <display-name></display-name> </tag>
Now in your JSP page, you can do this:
<%@ taglib uri="/WEB-INF/tld/taglib.tld" prefix="m" %> <html><body> <h1>Hello there!</h1> <h3><m:showNumberOfFriends /></h3> </body></html>