Lately I’ve been having a lot of fun playing around with the mingle api via active resource. I’ve developed a couple of small projects including one for reporting feature prioritization, as well a release notes generator. As it turns out, the release notes generator is a pretty big hit with our software support staff and the new feature requests are rolling in. Most recently a new feature required accessing the comment history of a story.

The Mingle api is powerful and simple and it makes little projects like these easy and fun. If you haven’t used the mingle api with active resource before, please take a look at my previous post (mingle automation with active resource) or this simple tutorial which I used to get started (tutorial on using mingle with active resource).

As part of our release notes we call out certain stories as having a high support implications, particularly if they modify some legacy database objects or legacy code. Our support department has found this to be instrumental in helping them quickly come up to speed on a new release. In addition to knowing which stories are high profile, it’s also important to know a little detail about these stories. Enter mingle card comments. As part of the release notes we want to include the last comment for each card under the assumption that developers will provide relavant comments before closing the card.

The mingle api documentation says that the comment attribute is only available in the historical versions of a card. Because of this, simply finding a card and grabbing the comment attribute will fail with an undefined method error.

require 'rubygems'
require 'activeresource'

class Card < ActiveResource::Base
  Project = "my_project"
  MyMingleServer = "mingle.weromans.com"
  self.site = "http://#{MyMingleServer}/projects/#{Project}"
end

Card.user = "mroman"
Card.password = "*******"
card = Card.find 555
# this will fail with undefined method
card.comment

The above code fails because I didn’t get a historical version of the card. To do that I need to specify the specific card version we want to retrieve. Since I want to get the last comment posted, I’m most likely going to loop backward through the version history until I find my comment. To do this I’ll need to start with my cards most recent version. I can just open the mingle UI to do that but since I’m already writing code, I’ll do it in an irb prompt, but first i’ll fix my Card class to get rid of that error.

require 'rubygems'
require 'activeresource'

class Card < ActiveResource::Base
  Project = "my_project"
  MyMingleServer = "mingle.weromans.com"
  self.site = "http://#{MyMingleServer}/projects/#{Project}"
end

Card.user = "mroman"
Card.password = "*******"

Now I’ll start up irb in the same directory as my card.rb file.


> require 'rubygems'
=> true
> require 'activeresource'
=> true
> require 'card'
=> ["Card"]
> (Card.find 555).version
=> 6

Great, the most recent version of my card is 6. Now all I should have to do is get version 6 of my card and access the comment attribute. Well, not really. For some reason getting the most recent version of a card doesn’t seem to qualify that card as a historical version which means that your card will not have the comment attribute and your code will still fail.

require 'rubygems'
require 'activeresource'

class Card < ActiveResource::Base
  Project = "my_project"
  MyMingleServer = "mingle.weromans.com"
  self.site = "http://#{MyMingleServer}/projects/#{Project}"
end

Card.user = "mroman"
Card.password = "*******"
card = Card.find(555, :params => {:version => 6})
# this will fail with undefined method
card.comment

If I change my code to get the previous version it appears to work.

require 'rubygems'
require 'activeresource'

class Card < ActiveResource::Base
  Project = "my_project"
  MyMingleServer = "mingle.weromans.com"
  self.site = "http://#{MyMingleServer}/projects/#{Project}"
end

Card.user = "mroman"
Card.password = "*******"
card = Card.find 555
historical_card = Card.find(card.number,
                            :params => {:version => card.version - 1})
card.comment

Success, kind of. The above code returned nil, but I’m taking that as a good sign. This means that my historical_card has a comment attribute, it most likely doesn’t have an actual comment though. If that’s the case all that’s left to do is clean up my code and create a loop to move backward through the card history until I find my comment.

require 'rubygems'
require 'activeresource'

class Card < ActiveResource::Base
  Project = "my_project"
  MyMingleServer = "mingle.weromans.com"
  self.site = "http://#{MyMingleServer}/projects/#{Project}"

  def Card.find_comment_for(number, version)
    card = Card.find(number, :params => {:version => version})
    card.comment
  end

  def Card.last_comment_for(card)
    version = card.version -1
    while ((comment = Card.find_comment_for(card.number, version)).nil?
            and version > 0)
      version -= 1
    end
    comment || "no comment"
  end
end

Card.user = "mroman"
Card.password = "*******"
Card.last_comment_for(Card.find(555))

This solves the requirement, however there is still the open question of what happens if the last comment was created on the last version of the card. Unfortunately as it stands I have not found a way to retrieve a comment on a cards current version.

 

Now for the ruby part. To review; in part one of the Ruby in Java with Maven series, we built a simple maven project which executes java 6 code and fires off a simple JavaScript using the built in JavaScript script engine. Now let’s try another language. If you recall from part 1, the only supported engine was the “Mozilla Rhino” engine which is the built in JavaScript engine. In order to run ruby, we’re going to have to add support for JRuby.

To do this, we’re going to need an implementation of the JRuby scripting engine. Fortunately the kind folks at the java scripting project have already implemented a bunch of scripting engines including JRuby. We just need to download the project, extract the necessary jar and import it into our maven repository to be included in our project. Here’s how…

  • Download the jsr223-engines project from the project downloads page
  • Extract the file to a local directory
  • cd to jsr223-engines/jruby/build
  • Execute the following command to import the lib to your maven repository

$ mvn install:install-file -Dfile=jruby-engine.jar -DgroupId=org.jruby -DartifactId=jruby-engine -Dversion=1.1 -Dpackaging=jar

Once the Jruby engine implementation is installed into your maven repository, you can include both the script engine and the Jruby implementation in your project. Edit your pom.xml to include the following 2 dependencies:

      ...

            org.jruby
            jruby
            1.1
            compile

            org.jruby
            jruby-engine
            1.1
            compile

Wow that was easy. We must have passed the inflection point where maven starts to become useful. If you want to verify that the ruby engine is now supported, you can rerun the code we wrote in part 1 that lists the supported engines.

Now lets write our awesome Ruby script. Create the directory src/main/ruby and store your script there.

# hello_world.rb
# this is a very complex ruby script
puts "Hello world"

Now modify your java to call the ruby script:

package com.weromans;

import java.io.FileReader;
import java.io.FileNotFoundException;
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class App
{
    public static void main( String[] args ) throws
                                             FileNotFoundException,
                                             ScriptException
    {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine rubyEngine = manager.getEngineByName("jruby");
        rubyEngine.eval(
                         new FileReader("src/main/ruby/hello_world.rb"));
    }
}

Now run it:
$ mvn exec:java
[INFO] Scanning for projects…
[INFO] ————————————————————————
[INFO] Building scripting
[INFO] task-segment: [exec:java]
[INFO] ————————————————————————
[INFO] Preparing exec:java
[INFO] No goals needed for project – skipping
Downloading: http://repo1.maven.org/maven2/org/jruby/jruby-engine/1.1/jruby-engine-1.1.pom
[INFO] Unable to find resource ‘org.jruby:jruby-engine:pom:1.1′ in repository central (http://repo1.maven.org/maven2)
[INFO] [exec:java]
Hello World
[INFO] ————————————————————————
[INFO] BUILD SUCCESSFUL
[INFO] ————————————————————————
[INFO] Total time: 2 seconds
[INFO] Finished at: Fri Apr 17 17:25:40 EDT 2009
[INFO] Final Memory: 5M/10M
[INFO] ————————————————————————

As you can see, the Hello world in the above output is from the ruby script. Congratulations, you’re running ruby in the JVM. Let’s take it a step further and pass some data to the ruby script from the Java. First we’ll modify our ruby script to print a variable.

# hello_world.rb
# this is a very complex ruby script
puts "Hello world, my name is #{$myname}"

We are now printing a global variable $myname in our ruby script. Let’s modify the java to pass that variable along to the ruby script.

package com.weromans;

import java.io.FileReader;
import java.io.FileNotFoundException;
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.script.ScriptContext;

public class App
{
    public static void main( String[] args ) throws
                                             FileNotFoundException,
                                             ScriptException
    {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine rubyEngine = manager.getEngineByName("jruby");

        ScriptContext context = rubyEngine.getContext();
        context.setAttribute("myname", "Matt", ScriptContext.ENGINE_SCOPE);

        rubyEngine.eval(new FileReader("src/main/ruby/hello_world.rb"));
    }
}

Run it again:
$ mvn compile exec:java
[INFO] Scanning for projects…
[INFO] ————————————————————————
[INFO] Building scripting
[INFO] task-segment: [compile, exec:java]
[INFO] ————————————————————————
[INFO] [resources:resources]
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory c:Documents and SettingsmromanMy Documentsworkspacejavascriptingscriptingsrcmainresources
Downloading: http://repo1.maven.org/maven2/org/jruby/jruby-engine/1.1/jruby-engine-1.1.pom
[INFO] Unable to find resource ‘org.jruby:jruby-engine:pom:1.1′ in repository central (http://repo1.maven.org/maven2)
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to c:Documents and SettingsmromanMy Documentsworkspacejavascriptingscriptingtargetclasses
[INFO] Preparing exec:java
[INFO] No goals needed for project – skipping
[INFO] [exec:java]
Hello World my name is Matt
[INFO] ————————————————————————
[INFO] BUILD SUCCESSFUL
[INFO] ————————————————————————
[INFO] Total time: 3 seconds
[INFO] Finished at: Fri Apr 17 17:36:02 EDT 2009
[INFO] Final Memory: 12M/22M
[INFO] ————————————————————————

Nice. You can see your line Hello World my name is Matt in the above output. That was pretty easy.

 

Java is a great language. It has many positive attributes, however one thing java is not good for is fast development. Now that I’m older and a little more impatient, I prefer to work in dynamic languages largely because of their short feedback cycle and instant gratification. Over the last few years, there’s been a movement towards the ability to run other languages within the JVM. This makes a lot of sense with the JVM being arguably the best part of Java. It makes it possible to use a dynamic language but still get some of the robustness and scalability of Java. Languages like Jython, Jruby and Jaskel are Java implementations of dynamic and/or functional programming languages that run in the context of a JVM and share a really nifty naming convention.

These languages have been in development for a while but until recently my team has only dabbled with some of them mainly for testing purposes. I haven’t really considered them a viable option for the “if I could only write this in Ruby” problem until recently. Java 6 has made integration of these languages very clean and easy.

The simple scripting inside java is easily demonstrable using javascript since the javascript engine is built into Java 6.

/* hello_world.js */
println("Hello World, I'm javascript")

I am going to attempt to run this super complex javascript from inside the JVM. The first thing I’m going to do is create a new Java project using Maven.

mvn archetype:create -DgroupId=com.weromans -DartifactId=scripting

Since the standard maven archetype doesn’t include a place to keep my scripts, I’ll create the directory ./scripting/src/main/javascript, and copy my hello_world.js file into it.

Just because I’m curious, I would like to see what scripting engines my environment currently supports. As I’ve already stated, javascript is supported out of the box, but let’s go ahead and verify that I’m not just lying to you for fun.

Open up the scripting/scr/main/java/com/weromans/App.java file in your favorite editor or IDE (vi if you’re hardcore), and modify it to include the following:

package com.weromans;

import java.util.List;
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngineFactory;

public class App
{
    public static void main( String[] args )
    {
        ScriptEngineManager manager = new ScriptEngineManager();
        List<ScriptEngineFactory> factoryList = manager.getEngineFactories();

        for (ScriptEngineFactory factory : factoryList) {
            System.out.printf("Engine Name: %s, Language: %sn",
                              factory.getEngineName(),
                              factory.getLanguageName());
        }
    }
}

Let’s just give Maven a little more information about our project so we can compile and run it easily from the command line. Edit your pom file to include the maven-compiler-plugin and the exec-maven-plugin.

<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">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.weromans</groupId>
    <artifactId>scripting</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>scripting</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.weromans.App</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Now just complie and run it:


$ mvn compile exec:java

You should see the following output:
[INFO] Scanning for projects…
[INFO] ————————————————————————
[INFO] Building scripting
[INFO] task-segment: [exec:java]
[INFO] ————————————————————————
[INFO] Preparing exec:java
[INFO] No goals needed for project – skipping
[INFO] [exec:java]
Engine Name: Mozilla Rhino, Language: ECMAScript
[INFO] ————————————————————————
[INFO] BUILD SUCCESSFUL
[INFO] ————————————————————————
[INFO] Total time: < 1 second
[INFO] Finished at: Fri Apr 17 14:14:45 EDT 2009
[INFO] Final Memory: 3M/6M
[INFO] ------------------------------------------------------------------------

the line Engine Name: Mozilla Rhino, Language: ECMAScript is your applications output and it show’s that the only engine name that is currently supported is Mozilla Rhino which is the javascript engine. Now let’s modify this thing to run our super complex hellow world javascript.

Open up the scripting/scr/main/java/com/weromans/App.java file in the hard core editor of your choice and modify it as follows:

package com.weromans;

import java.io.FileReader;
import java.io.FileNotFoundException;
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class App
{
    public static void main( String[] args ) throws
                                             FileNotFoundException,
                                             ScriptException
    {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine javascriptEngine = manager.getEngineByName("javascript");
        javascriptEngine.eval(
                         new FileReader("src/main/javascript/hello_world.js"));
    }
}

Execute it again:

$ mvn compile exec:java
[INFO] Scanning for projects…
[INFO] ————————————————————————
[INFO] Building scripting
[INFO] task-segment: [exec:java]
[INFO] ————————————————————————
[INFO] Preparing exec:java
[INFO] No goals needed for project – skipping
[INFO] [exec:java]
This is hello from hello_world.js
[INFO] ————————————————————————
[INFO] BUILD SUCCESSFUL
[INFO] ————————————————————————
[INFO] Total time: < 1 second
[INFO] Finished at: Fri Apr 17 15:27:26 EDT 2009
[INFO] Final Memory: 3M/6M
[INFO] ------------------------------------------------------------------------

You can see the hello world output from your javascript. Congratulations, you've just done some scripting in the JVM. I know the title says Ruby in Java, but it also says part 1, so you'll just have to check out Ruby in Java with Maven – Part 2 for the ruby part.

© 2012 WeRomans Suffusion theme by Sayontan Sinha