JRuby IRB and Java API - Client-Server Web Apps with JavaScript and Java (2014)

Client-Server Web Apps with JavaScript and Java (2014)

Appendix A. JRuby IRB and Java API

Human-machine interface styles have varied over time, based on the nature of the device in question as well as somewhat arbitrary trends. Command-Line Interfaces (or CLIs) were the primary means of interacting with operating systems before the 1990s. After that time, they were overshadowed by graphical operating systems and the visually dominated Internet. Despite their relatively humble appearance, CLIs remain popular due to functionality they provide that is not readily available through a GUI.

The pattern of interaction provided by a CLI reflects the functionality of teleprinters (gizmos that evolved from telegraph machines used to send typed messages). A CLI is more reliant on a user’s typing ability than a corresponding GUI. This limitation is also an advantage in that CLIs lend themselves to scripting. Most programmers have some experience in a CLI through the command-line shell of whatever operating system they are using. When tasks become more involved, it is simple to bundle a set of commands together into a script. This capability has resulted in many scripting languages, including their own CLIs as an execution environment.

A CLI might also be referred to as a REPL (Read-Eval-Print Loop) or a language shell. Whatever you call them, they are invaluable for exploring language features and getting immediate feedback on the effect of running a given expression or command.

Though Java itself does not include a CLI (Beanshell is the closest equivalent), many of the languages supported on the JVM do include (but are not limited to) JRuby, Jython, and Groovy. The close integration of JVM languages with Java makes it possible to interact through CLIs for each of these languages with native Java classes and modules. This is a significantly different style of workflow that can be very helpful when experimenting and debugging. In this chapter, JRuby’s IRB (Interactive Ruby shell) is used to execute SQL queries through JDBC connections to a number of Java-based databases.

WHY NOT IRS?

You might wonder why Ruby does not call its command-line interface IRS (Interactive Ruby Shell). The standard file extension for Ruby programs is .rb. Hence “Interactive” plus “RB” results in IRB.

Setup Using Gradle

Gradle will be used to download the set of Java project dependencies used in the following scripts. Note that this is simply a convenience; there is no necessary connection between a build tool like Gradle and a CLI. This Gradle build file was initially generated using gradle setupBuild. The resulting build.gradle file included comments related to usage. The file was then modified to include the modules needed as well as relevant plug-ins and the repository where they are hosted:

apply plugin:'java'

apply plugin:'application'

repositories{mavenCentral()}

dependencies{

compile 'org.slf4j:slf4j-api:1.7.5'

compile 'hsqldb:hsqldb:1.8.0.10'

compile 'com.h2database:h2:1.3.172'

compile 'net.sf.opencsv:opencsv:2.3'

compile 'commons-io:commons-io:2.4'

compile 'org.apache.derby:derby:10.10.1.1'

testCompile "junit:junit:4.11"

}

With the build file defined, the project can be built using gradle build. The necessary JARs will be added to your local repository. Again, Gradle is not specifically required for this task. You could instead use Maven, or even manually locate and download each JAR used. Gradle was selected because of its minimal syntax (compared to Maven) and because manually downloading files is error-prone and tedious.

JRuby IRB

Because the following example uses Java classes (the database implementations and JDBC), a Java-based version of Ruby is required. Other implementations will not function. If you are using RVM, install JRuby (if necessary) and select it for use:

$ rvm use jruby 1.7.4

Install the bundler gem if you have not yet done so (version 1.3.5 was used in this example). When this is available, run bundle init to create the Gemfile used by bundle. Add the following three lines:

gem 'jdbc-derby', '10.9.1.0'

gem 'jdbc-h2', '1.3.170.1'

gem 'jdbc-hsqldb', '2.2.9.1'

These gems wrap the JDBC drivers used by JRuby. Run bundle to install these (Ruby) dependencies.

We have seen Java dependencies managed by Gradle, and JRuby dependencies managed by bundler. It is also possible to reference Java JARs directly from JRuby. For convenience, copy the JAR (included in the Gradle init) in a lib directory under our current location. The file will be inside your local Gradle repository:

$ find ~/.gradle -name opencsv-2.3.jar

$ mkdir lib

$ cp <path to jar file from find command>opencsv-2.3.jar lib

The JAR will be accessed from within JRuby using the require keyword (which is generally used for importing Ruby files).

Intro to IRB

Logging in to IRB will bring you to a prompt. The specific prompt will vary depending on the version of Ruby you are using:

$ irb

jruby-1.7.4 :001 >

From this prompt, you can enter an expression and see its immediate evaluation:

2.0.0-p247 :001 > 1 + 4

=> 5

You can also inspect objects and find out what functionality they offer. Reflection in Java allows objects to be explored and manipulated but is also verbose and complex. By contrast, Ruby provides straightforward access to dynamically interrogate and alter objects. It is well known for its meta-programming capabilities due to its flexibility in this regard:

2.0.0-p247 :002 > "Hello World".class

=> String

2.0.0-p247 :014 > "Hello World".methods.grep(/sp/).sort

=> [:display, :inspect, :respond_to?, :split]

2.0.0-p247 :021 > "Hello World".split

=> ["Hello", "World"]

For the remaining examples, the prompt and the result will be omitted. But the feedback provided immediately after running a command is the real value of interacting in irb and is best experienced to be fully appreciated.

In the preceding example, Hello World is a string (but is not assigned to any particular variable). It has a large number of methods available, so the example filters (using grep) and orders the ones that contain sp. Finally, having found a method in this manner, we can actually call it on the object (the Hello World string) and see its effect. This approach is not a substitute for referencing documentation, but does render many of the lookups you do otherwise unnecessary.

Java-Based Relational Databases

Within an irb session, we can now explore the APIs available for several Java-based relational databases. If you have ever written a Java class that uses JDBC, you will undoubtedly recall that there is a fair amount of boilerplate code required. Beyond the standard Java requirements (defining a class with a main method), you need to write a good deal of code related to exception handling (import statements, throws clauses in method declarations, try/catch blocks). Add in the additional syntax required for explicitly typing, a bit of output, and a few comments, and a seemingly simple class can become rather bloated. Fortunately, Ruby’s syntax is concise, and the interactive environment makes it easy to test drive the APIs.

The three databases we will look at are H2, HSQLDB, and Derby. They are similar from the outside, differing in specific implementation and storage mechanisms, performance, and open source licensing options. Each is accessible via JDBC, and in an irb session we will test each type of SQL statement available, as shown in Table A-1.

Table A-1. SQL statement types

Type

Example

Description

Query

SELECT

Retrieve data

DDL

CREATE, DROP

Data definition language (create, alter, or replace a database object)

DML

INSERT, UPDATE, DELETE

Data manipulation language (modify data)

As it turns out, there are actually some slight variations between databases that are only evident when you actually interact with them (such as SQL syntax, connection strings, and closing of result sets).

To make these examples a bit more concise, a common code required for each can be added to a file and loaded from within irb. This includes code to make Java available, load up the opencsv JAR (used to quickly render a result set as comma-separated values), and add functions to execute SQL (queries, DML, and DDL) and to display SQL result sets:

require 'java'

require 'lib/opencsv-2.3.jar'

TEMP_FILE="temp.csv"

def displayResultSet(r)

writer = Java.AuComBytecodeOpencsv::CSVWriter.new(

java.io.FileWriter.new(TEMP_FILE),

java.lang.String.new("\t").charAt(0)

)

writer.writeAll(r, true)

writer.close()

File.open(TEMP_FILE).readlines.each{|line|puts line}

`rm #{TEMP_FILE}`

end

def exec(statement, conn)

puts statement

conn.createStatement().execute(statement)

end

def execQuery(statement, conn)

puts statement

conn.createStatement().executeQuery(statement);

end

This file is actually loaded into the environment by running load 'dbutils.rb'.

H2

Thomas Mueller created H2 and was also heavily involved in the development of HSQLDB. In the following example, a username of sa with a blank password is used to connect to a database named test where SQL statements are then executed. Also note that no VARCHAR length is specified for the name column when the table is created:

load 'dbutils.rb'

require 'jdbc/h2'

Jdbc::H2.load_driver

conn = java.sql.DriverManager.getConnection('jdbc:h2:test', "sa", "")

# VARCHAR does not require a length

exec("CREATE TABLE test (id int, name varchar)", conn)

exec("INSERT INTO test(id, name) VALUES (1, 'a')", conn)

displayResultSet(execQuery('select * from test', conn))

exec("DROP TABLE test", conn)

conn.close()

HSQLDB

HSQLDB is known for its inclusion in open source projects like OpenOffice as well as commercial products like Mathematica. Unlike H2, a username and password are not required when getting the connection, and a VARCHAR length is required in the name column:

load 'dbutils.rb'

require 'jdbc/hsqldb'

Jdbc::HSQLDB.load_driver

conn = java.sql.DriverManager.getConnection('jdbc:hsqldb:test')

exec("CREATE TABLE test (id int, name varchar(10))", conn)

exec("INSERT INTO test(id, name) VALUES (1, 'a')", conn)

displayResultSet(execQuery('select * from test', conn))

exec("DROP TABLE test", conn)

conn.close()

Derby

Derby is an Apache project whose lineage goes back to the 1990s. It has continued through various incarnations at CloudScape, Informix, and IBM. Since Java 6, Sun (later acquired by Oracle) included Derby in the JDK as Java DB. The create=true attribute in the JDBC connection string indicates that a database be created if it does not exist when the connection is requested. In addition, Derby requires a result set to be explicitly closed prior to dropping a table that references it:

load 'dbutils.rb'

require 'jdbc/derby'

Jdbc::Derby.load_driver

conn = java.sql.DriverManager.getConnection('jdbc:derby:test;create=true')

exec("CREATE TABLE test (id int, name varchar(10))", conn)

exec("INSERT INTO test(id, name) VALUES (1, 'a')", conn)

r = execQuery('select * from test', conn)

displayResultSet(r)

r.close()

exec("DROP TABLE test", conn)

conn.close()

Although relational databases were the focus of the previous example, it should be obvious that any Java library can be accessed via a script and explored using a similar process.

Conclusion

The immediate feedback provided by CLIs makes them a particularly effective tool despite the move toward graphical methods. Scripts can be constructed by iterative experiments within a CLI, and this process removes not only the traditional build step but even the need to specifically execute a source file. This type of interaction is well understood by many developers, but those who have focused on Java might have had limited exposure. The JavaScript console available in modern web browsers is a modern implementation of a CLI, and as this chapter illustrates, similar server-side processing is available to Java programmers with just a bit of initial setup.