Google IO: Thoughtworks on GAE Procs, lambdas, blocks?! What’s the difference?
Jul 01

The motivation for this post came from a couple of messages I’ve seen on the jruby’s google group and because I think it’s pretty cool to share how we tackled this problem.

- A little bit of context

We, as a vast amount of people out there, have legacy Java code. A lot. In our case this legacy is pretty much crucial to our business. We can’t just trash it and start from scratch. Bad idea.

On the other hand we do have new features to be built on top of it. But we wanted an easier way to develop this new stuff and decided for a JRuby on Rails solution, using it as a front-end to our existing services.

- What we decided to do

Our final rails project would make use of a specially created jar file containing our Java application. This Jar would also contain a public interface of the services we’d have to interact with from rails.

As any Java application, ours depend on a number of external jar files that correspond to the various framewoks we usually have in place. e.g.: Hibernate, Spring, apache-commons …

Which means we need to make our app’s jar and all it’s dependencies available in the JRuby classpath in order to use it.

Given we’re using warbler to package our application as a war file, we just need to place all jars needed into our rails app’s lib folder. Warbler then takes care of copying any jar files located in there into the war.

- The problem

So we needed a smart way to include all these dependencies into the project, and copy/paste isn’t an option.

In the Java world we use Maven to manage our projects dependencies - and you should too. Because of that our approach involved turning our rails application into a Maven aware project.

Basically we needed a pom file that would declaratively list our java project as a dependency. From there on, Maven knows what the dependencies are and downloads them to your local repository.

Which leaves us with one more task. We need to put all these dependencies into our lib folder after maven has downloaded them.

Below you’ll find the pom.xml file that we use to achieve this with inline comments explaining each bit:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.company</groupId>
  <!-- notice how we specify the packaging to be a war,
          that way, maven knows where to copy the jar files -->
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <artifactId>railsApp</artifactId>
  <name>railsApp</name>
    <dependencies>
        <dependency>
            <groupId>com.company</groupId>
            <artifactId>java-legacy-app</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>railsApp</finalName>
        <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <executions>
          <execution>
            <!-- This tasks only creates a basic structure expected by maven,
                    so it can do its work -->
            <id>create-mock-web-descriptor</id>
            <phase>compile</phase>
            <configuration>
              <executable>/bin/sh</executable>
              <workingDirectory>.</workingDirectory>
              <arguments>
                <argument>-c</argument>
                <argument>
                    mkdir -p src/main/webapp/WEB-INF
                    touch    src/main/webapp/WEB-INF/web.xml
                </argument>
              </arguments>
            </configuration>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
          <execution>
            <!-- Now in the package phase we copy the jar files
                    that maven put into the fake web app to our rails' lib folder -->
            <id>copy-needed-jars-into-lib</id>
            <phase>package</phase>
            <configuration>
              <executable>/bin/sh</executable>
              <workingDirectory>.</workingDirectory>
              <arguments>
                <argument>-c</argument>
                <argument>
                    rm -f lib/*.jar
                    cp target/railsApp/WEB-INF/lib/*.jar lib
                    rm -rf target/railsApp*
                    rm -rf src
                </argument>
              </arguments>
            </configuration>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
          <execution>
           <!-- Here we optionally create the final war file containing our rails app using warbler,
                     doing a small cleanup of the files and folders maven created  -->
            <id>create-final-war</id>
            <phase>package</phase>
            <configuration>
              <executable>/bin/sh</executable>
              <workingDirectory>.</workingDirectory>
              <arguments>
                <argument>-c</argument>
                <argument>
                   rm -rf *.war tmp/war
                   jruby -S warble &amp;&amp; \
                   mv *.war target/railsApp.war
                </argument>
              </arguments>
            </configuration>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
     </plugins>
    </build>
</project>

Now from the command line we can just run mvn package and we’re good to go.

Maven will start to package the application as a war file. Since it’s not a Java application we create the empty web.xml file in the compile phase, to fool maven.

After it has copied all the dependencies into WEB-INF/lib the next packaging goals will make sure we copy them to our rails’ lib folder, also creating the final war file, ready for deployment.

Note that once done, you can use a simple code snippet similar to this one as an initializer and load all dependencies:

Dir.entries("#{RAILS_ROOT}/lib").sort.each do |entry|
  if entry =~ /.jar$/
    require entry
  end
end

Then we can just use script/console, script/server and so on, as we normally would.

Sorry for the long post, I tried to pack in as much as I could and I certainly hope it’s useful to someone. Any doubts, comments and etc… just drop me a line. :)

Related Posts

8 Responses to “JRuby on Rails and legacy java apps: Managing dependencies”

  1. James Abley says:

    Nice. I’ve been meaning to post something similar, but moving house has sucked up all my free time. I’ll write it up Real Soon Now.

    What about people on not on a Unix? We used the maven-ant-plugin for that reason.

  2. HI James!

    We didn’t bother with the environment because everyone here is on Unix. But I do believe that if someone needs this in another env, say windows, it would be easy enough to adapt it.
    Or as you suggested, using the maven-ant-plugin. Haven’t really thought of that since the shell based solution seemed more straightforward to us.

    Cheers and tks for stopping by!

  3. nick says:

    I was thinking about using maven to help manage jruby application dependencies as well. I was thinking about using the dependency plugin (http://maven.apache.org/plugins/maven-dependency-plugin/) to copy dependencies to the lib directory, because right now I’m not warbling up the project. I bet there are several different ways to go about this in maven, depending on the exact workflow and desired packaging. Nice work!

  4. Hi nick!
    Even though we warble the project here, you can still use this approach. You’d only need to remove the last part of the pom.xml, the one that warbles the app. The steps previous to this only copy the necessary jars to your rails application lib folder.

    But you’re right, with maven, you can go in a number of ways about this. I’ll make sure I check this dependency plugin, didn’t know it.

    Tks!

  5. ChetanM says:

    Hi

    js compressions have lots of trouble with rails-jruby.
    bash executions in pom.xml solved the problem .

    Nice work!

  6. Hello Chetan! Care to elaborate more on your last comment?

    I didn’t quite get what you meant by “bash executions in the pomxml” and how that fits with the JS Compressions issue.

  7. Thank you so much for this article! This is exactly what I need and it works perfectly!

    One related thing is that if your root package is not “java”, “com”, or “org”, you will need to qualify it, e.g.

    Java::myroot.foo.bar.MyClass.new

  8. Thanks Dave,
    I’m glad it was useful for you.

    And tks for sharing that bit of info. Every project I worked so far with jruby, we had roots starting with “com”. No wonder I never stumbled upon this issue.

    []’s

Leave a Reply

preload preload preload