Packaging_J2EE_Web_Applications

john_brock edited this page Jan 6, 2011 · 1 revision
Clone this wiki locally

Table of Contents

This was conceptual work at an early phase of the project.

This has now been superseded by rails-integration which goes a long way towards automating the process.


Java web applications are typically packaged as WAR files in preparation for distribution and deployment to J2EE servers. It will be useful to be able to package Ruby on Rails applications in a similar form, to enable seamless deployment to Java servers.

The entry point for a web application is through a servlet. This will fill the role of Rails dispatcher.rb, and must be responsible for initializing the JRuby environment, and dispatching the request to an appropriate controller. From this point there are at least two approaches:

  1. Fill the environment, and call the usual Rails CGI dispatcher
  2. Implement the Rails request/response interfaces using the Java Servlet API

The first approach has been used successfully by others, however for this I chose to take the second approach, because it allows the existing Java Servlet functionality to be easily leveraged for other uses, such as session handling.

Quick start

The sections below explain how things are setup. To get started quickly, just download the package and follow these steps:

  1. Modfiy pom.xml and set the artifactId (this is also the name of the WAR)
  2. Replace src/main/rails with your Rails app
  3. Modify src/main/webapp/WEB-INF/web.xml, and add a servlet-mapping for each of your routes/controllers
  4. Build with mvn package
  5. Try it out with mvn jetty:run-war, and point your browser to http://localhost:8080/

Project layout

I've used a Maven 2 project layout so that Maven can assembly the WAR, however other options are certainly possible.

Location Description
/myrailsapp/pom.xml the Maven 2 project descriptor
/myrailsapp/src/main/rails this is where your rails application goes
/myrailsapp/src/main/webapp/WEB-INF/web.xml the webapp descriptor

Additionally the dispatcher code has been placed in these two folders. Ideally this code would be either in a separate library or as part of JRuby, so these paths would not be required.

Project descriptor

The project descriptor must assemble the WAR file such that the dispatcher can easily find the Rails code.

We'll require at least the following dependencies:

<dependency>
	<groupId>org.jruby</groupid>
	<artifactId>jruby</artifactid>
	<version>0.9.1</version>
</dependency>
<dependency>
	<groupId>javax.servlet</groupid>
	<artifactId>servlet-api</artifactid>
	<version>2.4</version>
	<scope>provided</scope>
</dependency>

We need to direct the war plugin to arrange the extra directories correctly.

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupid>
			<artifactId>maven-war-plugin</artifactid>
			<version>2.0.2-SNAPSHOT</version>
			<configuration>
				<webResources>
					
					<resource>
						<directory>src/main/rails</directory>
						<targetPath>WEB-INF/rails</targetpath>
						<excludes>
							<exclude>public/</exclude>
						</excludes>
					</resource>
					
					<resource>
						<directory>src/main/rails/public</directory>
					</resource>
					
					<resource>
						<directory>src/main/ruby</directory>
						<targetPath>WEB-INF/ruby</targetpath>
					</resource>
				</webresources>
			</configuration>
		</plugin>
		
		<plugin>
			<groupId>org.mortbay.jetty</groupid>
			<artifactId>maven-jetty-plugin</artifactid>
		</plugin>
	</plugins>
</build>

Only the 2.0.2+ versions of the war plugin support targetPath, so let's include the snapshot repository.

<pluginRepositories>
	<pluginRepository>
		<id>apache.org</id>
		<name>Maven Plugin Snapshots</name>
		<url>http://people.apache.org/maven-snapshot-repository</url>
		<releases>
			<enabled>false</enabled>
		</releases>
		<snapshots>
			<enabled>true</enabled>
		</snapshots>
	</pluginrepository>
</pluginrepositories>

Rails

A standard rails application can be placed under /myrailsapp/src/main/rails.

To do this, just run "rails myrailsapp/src/main/rails".

After this is created, you may need to ensure that any empty directories (e.g. logs), have at least one file in there. One way is to add a README file describing what the directory is for. This is because the war plugin will ignore empty directories.

Web application descriptor

The web application descriptor (web.xml) so be placed in /myrailsapp/src/main/webapp/WEB-INF/web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">

	
	<context-param>
		<param-name>jruby.home</param-name>
		<param-value>/Users/regg002/Reference/jruby-0.9.1</param-value>
	</context-param>

	
	<servlet>
		<servlet-name>rails</servlet-name>
		<servlet-class>org.jruby.webapp.RailsServlet</servlet-class>
	</servlet>

	
	<servlet-mapping>
		<servlet-name>rails</servlet-name>
		<url-pattern>/rails/*</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>rails</servlet-name>
		<url-pattern>/mycontroller/*</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>rails</servlet-name>
		<url-pattern>/myothercontroller/*</url-pattern>
	</servlet-mapping>

	
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
</web-app>

Dispatcher

This dispatcher is where the real integration work occurs. This is a two-step process:

  • RailsServlet, the Java servlet and entry point for the webapp.
  • java_servlet_dispatcher.rb, the Ruby implementation of the Rails dispatcher, request and response classes, which makes use of the Servlet API.

RailsServlet

RailsServlet must perform the following actions:

  1. Find the Ruby home directory and Rails application
  2. Setup the initial load paths
  3. Initialize JRuby
  4. Initialize Rails by requiring "rails/config/environment" (not "rails/config/environment.rb")
  5. Create an instance of java_servlet_dispatcher.rb
  6. On every request, delegate to JavaServletDispatcher.dispatch, passing through HttpServletRequest and HttpServletResponse

JavaServletDispatcher

JavaServletDispatcher must extend the Rails Dispatcher, however, rather than following the normal process of using the CGI request/response objects, it will construct instances of Rails request and response objects which are implemented using the Servlet API. These wrapper classes are called JavaServletRequest and JavaServletResponse.

JavaSession is also included, this is used by JavaServletRequest, and implements Rails session handling using the HttpSession.