Showing posts with label JSR 223. Show all posts
Showing posts with label JSR 223. Show all posts

Saturday, July 24, 2010

Bookmark and Share

In the last couple of days I spent some time experimenting a little bit with CDI, the standard of the Java EE 6 platform for dependency injection services.

I must say that I really like that spec, as it hits the sweet spot between specifying features that provide a value out of the box (type-safe DI, eventing, interceptor services etc.) and being open enough to allow people to build totally new stuff based on it (using portable extensions).

I've got the feeling we're going to see a lot of exciting things based on CDI within the near future. Actually this reminds me a bit of Java annotations. Having been introduced with Java 5, just over time people started using them for more and more use cases that had not been foreseen in the first place.

Anyway, to do something practical with CDI I built a small portable extension which allows to retrieve JSR 223 scripting engines using dependency injection.

Just annotate any injection points of type javax.script.ScriptEngine with one of the qualifier annotations @Language, @Extension or @MimeType. The following code extract shows an example of a JavaScript engine (for example the Rhino engine shipping with Java 6) being injected into some managed bean, where it can be used for arbitrary script evaluations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestScoped
public class MyBean {

    @Inject 
    @Language("javascript") 
    private ScriptEngine jsEngine;

    // ...

    public void foo() throws ScriptException {

        assert 42.0d == (Double)jsEngine.eval("2 * 21");
        // ...
    }
}

The extension can be found in my Maven repository. Just add the following dependency to your POM in order to use it:

1
2
3
4
5
<dependency>
    <groupId>de.gmorling.cdi.extensions</groupId>
    <artifactId>scripting-extension</artifactId>
    <version>0.1</version>
</dependency>

In case you want to take a look at the source code (which is just a few lines), you can check it out from GitHub. As always, any ideas for improvement or other feedback are highly appreciated.

Monday, June 28, 2010

Bookmark and Share

I'm very proud to report that today Hibernate Validator 4.1.0 Final has been released. Besides many bug fixes this release adds also a lot of new features to the code base.

The changes fall into four areas, which I'm going to discuss in detail in the following:

  • New constraint annotations
  • ResourceBundleLocator API
  • API for programmatic constraint creation
  • Constraint annotation processor

New constraint annotations

In addition to the constraints defined in the Bean Validation spec and those custom constraints already part of HV 4.0, the new release ships with the following new constraint annotations:

  • @CreditCardNumber: Validates that a given String represents a valid credit card number using the Luhn algorithm. Useful to detect mis-entered numbers for instance.
  • @NotBlank: Validates that a given String is neither null nor empty nor contains only whitespaces.
  • @URL: Validates that a given String is a valid URL. Can be restricted to certain protocols if required: @URL(protocol = "http") private String url;
  • @ScriptAssert: Allows to use scripting or expression languages for the definition of class-level validation routines.

Let's have a closer look at the @ScriptAssert constraint, which I'm particularly excited about as I have implemented it :-).

The intention behind it is to provide a simplified way for expressing validation logic that is based on multiple attributes of a given type. Instead of having to implement dedicated class-level constraints the @ScriptAssert constraint allows to express such validation routines in an ad hoc manner using a wide range of scripting and expression languages.

In order to use this constraint an implementation of the Java Scripting API as defined by JSR 223 ("Scripting for the JavaTM Platform") must be part of the class path. This is automatically the case when running on Java 6. For older Java versions, the JSR 223 RI can be added manually to the class path.

As example let's consider a class representing calendar events. The start date of such an event shall always be earlier than the end date. Using JavaScript (for which an engine comes with Java 6) this requirement could be expressed as follows:

1
2
3
4
5
6
7
8
9
@ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)")
public class CalendarEvent {

    private Date startDate;

    private Date endDate;

    //...
}

So all you have to do is to specify a scripting expression returning true or false within the script attribute. The expression must be implemented in the language given within the language attribute. The language name must be the language's name as registered with the JSR 223 ScriptEngineManager. Within the expression the annotated element can be accessed using the alias _this by default.

The cool thing is that the @ScriptConsert constraint can be used with any other scripting language for which a JSR 223 binding exists. Let's take JEXL from the Apache Commons project for instance. Using Maven you only have to add the following dependency:

1
2
3
4
5
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl</artifactId>
    <version>2.0.1</version>
</dependency>

With JEXL dates can be compared using the "<" operator. Using a shorter alias for the evaluated object the constraint from above therefore can be rewritten as follows:

1
2
3
4
5
6
7
8
9
@ScriptAssert(lang = "jexl", script = "_.startDate < _.endDate", alias = "_")
public class CalendarEvent {

    private Date startDate;

    private Date endDate;

    //...
}

Very likely one will work with only one scripting language throughout all @ScriptAssert constraints of an application. So let's leverage the power of constraint composition to create a custom constraint which allows for an even more compact notation by setting the attributes lang and alias to fixed values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Documented
@ScriptAssert(lang = "jexl", script = "", alias = "_")
public @interface JexlAssert {

    String message() default "{org.hibernate.validator.constraints.ScriptAssert.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @OverridesAttribute(constraint = ScriptAssert.class, name = "script")
    String value();
}

Note how the script attribute of the composing @ScriptAssert constraint is overridden using the @OverridesAttribute meta-annotation. Using this custom constraint the example finally reads as follows:

1
2
3
4
5
6
7
8
9
@JexlAssert("_.startDate < _.endDate")
public class CalendarEvent {

    private Date startDate;

    private Date endDate;

    //...
}

As shown the @ScriptAssert constraint allows to define class-level constraints in a very compact way.

But there is also a price to pay. As scripting languages are used, compile-time type-safety is lost. When for instance the startDate attribute is renamed the script expression would have to be adapted by hand. Also evaluation performance should be considered when validation logic is getting more complex.

So I recommend to try it out and choose what ever fits your needs best.

The ResourceBundleLocator API

The Bean Validation API defines the MessageInterpolator interface which allows to plug in custom strategies for message interpolation and resource bundle loading.

As it turned out, most users only want to customize the latter aspect (e.g. in order to load message bundles from a database) but would like to re-use the interpolation algorithm provided by Hibernate Validator.

Therefore Hibernate Validator 4.1 introduces the interface ResourceBundleLocator which is used by HV's default MessageInterpolator implementation ResourceBundleMessageInterpolator to do the actual resource bundle loading.

The interface defines only one method, in which implementations have to return the bundle for a given locale:

1
2
3
4
5
public interface ResourceBundleLocator {

    ResourceBundle getResourceBundle(Locale locale);

}

The ResourceBundleLocator to be used can be set when creating a ValidatorFactory:

1
2
3
4
5
6
7
ValidatorFactory validatorFactory = Validation
    .byProvider(HibernateValidator.class)
    .configure()
    .messageInterpolator(
        new ResourceBundleMessageInterpolator(
            new MyCustomResourceBundleLocator()))
    .buildValidatorFactory();

The default ResourceBundleLocator implementation used by Hibernate Validator is PlatformResourceBundleLocator which simply loads bundles using ResourceBundle.loadBundle(). Another implementation provided out of the box is AggregateResourceBundleLocator, which allows to retrieve message texts from multiple bundles by merging them into a single aggregate bundle. Let's look at an example:

1
2
3
4
5
6
7
8
9
10
11
HibernateValidatorConfiguration configuration = Validation
    .byProvider(HibernateValidator.class)
    .configure();

ValidatorFactory validatorFactory = configuration
    .messageInterpolator(
        new ResourceBundleMessageInterpolator(
            new AggregateResourceBundleLocator(
                Arrays.asList("foo", "bar"), 
                configuration.getDefaultResourceBundleLocator())))
    .buildValidatorFactory();

Here messages from bundles "foo" and "bar" could be used in constraints. In case the same key was contained in both bundles, the value from bundle "foo" would have precedence, as it comes first in the list. If a given key can't be found in any of the two bundles as fallback the default locator (which provides access to the ValidationMessages bundle as demanded by the JSR 303 spec) will be tried.

API for programmatic constraint creation

Using the Bean Validation API constraints can be declared using either annotations and/or XML descriptor files. Hibernate Validator 4.1 introduces a third approach by providing an API for programmatic constraint declaration.

This API can come in handy for instance in dynamic scenarios where constraints change upon runtime or for testing scenarios.

As example let's consider two classes, Customer and Order, from a web shop application for which the following constraints shall apply:

  • each customer must have a name
  • each order must have a customer
  • each order must comprise at least one order line

With help of the programmatic constraint API these constraints can be declared as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
ConstraintMapping mapping = new ConstraintMapping();

mapping
    .type(Order.class)
        .property("customer", ElementType.FIELD)
            .constraint(NotNullDef.class)
        .property("orderLines", ElementType.FIELD)
            .constraint(SizeDef.class)
                .min(1)
                .message("An order must contain at least one order line")
    .type(Customer.class)
        .property("name", ElementType.FIELD)
            .constraint(NotNullDef.class);  

As the listing shows the API is designed in a fluent style with the class ConstraintMapping as entry point. Constraints are declared by chaining method calls which specify to which property of which type which constraints should be added.

The API provides constraint definition classes such as NotNullDef etc. for all built-in constraints, which allow to access their properties (min(), message() etc.) in a type-safe way. For custom constraints you could either provide your own constraint definition class or you could make use of GenericConstraintDef which allows to identify properties by name.

Having created a constraint mapping it has to be registered with the constraint validator factory. As the programmatic API is not part of the Bean Validation spec, we must explicitely specify Hibernate Validator as the BV provider to be used:

1
2
3
4
5
6
Validator validator = Validation
    .byProvider(HibernateValidator.class)
    .configure()
    .addMapping(mapping)
    .buildValidatorFactory()
    .getValidator();

If you now use this validator to validate an Order object which has no customer set a constraint violation will occur, just as if the "customer" property was annotated with @NotNull.

Constraint annotation processor

The Hibernate Validator annotation processor might become your new favourite tool if you find yourself accidentally doing things like

  • annotating Strings with @Min to specify a minimum length (instead of using @Size)
  • annotating the setter of a JavaBean property (instead of the getter method)
  • annotating static fields/methods with constraint annotations (which is not supported)

Normally you would notice such mistakes only during run-time. The annotation processor helps in saving your valuable time by detecting these and similar errors already upon compile-time by plugging into the build process and raising compilation errors whenever constraint annotations are incorrectly used.

The processor can be used in basically every build environment (plain javac, Apache Ant, Apache Maven) as well as within all common IDEs. Just be sure to use JDK 6 or later, as the processor is based on the "Pluggable Annotation Processing API" defined by JSR 269 which was introduced with Java 6.

The HV reference guide describes in detail how to integrate the processor into the different environments, so I'll spare you the details here. As example the following screenshot shows some compilation errors raised by the processor within the Eclipse IDE (click to enlarge):

Hibernate Validator Annotation Processor in Eclipse IDE

Just for the record it should be noted that the annotation processor already works pretty well but is currently still under development and is considered as an experimental feature as of HV 4.1. If you are facing any problems please report them in JIRA. Some known issues are also discussed in the reference guide.

Summary

The focus for Hibernate Validator 4.0 was to provide a feature-complete, production-ready reference implementation of the Bean Validation spec.

While strictly staying spec-compliant, HV 4.1 goes beyond what is defined in JSR 303 and aims at generating even more user value by providing new constraints, new API functionality as well as an annotation processor for compile-time annotation checking.

In order to try out the new features yourself just download the release from SourceForge. Of course HV 4.1 can also be retrieved from the JBoss Maven repository.

If you are already using HV 4.0.x, the new release generally should work as drop-in replacement. The only exception is that we had to move the class ResourceBundleMessageInterpolator to the new package messageinterpolation. So if you're directly referencing this class, you'll have to update your imports here.

The rationale behind this relocation was to clearly separate those packages which can safely be accessed by HV users from packages intended for internal use only. The public packages are:

  • org.hibernate.validator
  • org.hibernate.validator.cfg
  • org.hibernate.validator.constraints
  • org.hibernate.validator.messageinterpolation
  • org.hibernate.validator.resourceloading

Of course we'd be very happy on any feedback. Questions and comments can be posted in the HV forum, while any issues or feature requests should be reported in JIRA.

Monday, August 24, 2009

Bookmark and Share

JSR 303 ("Bean Validation") is the upcoming standard for the declaration and evaluation of constraints at Java object models, either by using annotations or XML descriptors.

Having been in the works for quite a while, the Bean Validation API comprises a lot of the features of previously existing validation frameworks such as Hibernate Validator or the OVal framework.

One feature I'm particularly loving about OVal is not part of the new standard API: the possibility to define constraints using scripting or expression languages. This is quite practical, if the validation of one bean property depends on the value of another property of the same bean. Imagine for instance a calendar application, where the start date of a calendar event shall always be earlier than the end date.

Using the Bean Validation API, this problem could be solved by implementing a custom class-level constraint. Unfortunately this requires you to implement a dedicated constraint, whenever such inter-property validation is required.

With the help of a generic script annotation that takes an arbitrary script expression to be evaluated, this effort can be saved (at the cost of losing some compile-time safety, though). By using a simple Groovy expression for instance the constraint mentioned above could be expressed like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@ScriptAssert(lang = "groovy", script = "_this.startDate.before(_this.endDate)")
public class CalendarEvent {

    private String title;

    private Date startDate;

    private Date endDate;

    public CalendarEvent(String title, Date startDate, Date endDate) {

        this.title = title;
        this.startDate = startDate;
        this.endDate = endDate;
    }

    public String getTitle() {
        return title;
    }

    public Date getStartDate() {
        return startDate;
    }

    public Date getEndDate() {
        return endDate;
    }

}

But how to implement the ScriptAssert constraint shown in the example? Luckily the JSR 303 spec was designed to make it really easy to create custom constraints, so this is not a big dial. Basically it takes three steps to create a custom constraint:

  • define a constraint annotation
  • implement a validator class able to check the constraint
  • define an error message for the case that the constraint is violated

For the evaluation of script statements, we will leverage the scripting API defined by JSR 223 ("Scripting for the JavaTM Platform"), which is part of the JDK since Java 6. By doing so arbitrary scripting and expression languages, for which a JSR 223 compatible engine exists, can be used in the ScriptAssert annotation (a list of such engines can be found here).

Defining the annotation

At first we have to define the annotation type itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = ScriptAssertValidator.class)
@Documented
public @interface ScriptAssert {

    String message() default "{de.gmorling.beanvalidation.scriptassert}";

    Class<?>[] groups() default {};

    Class<? extends ConstraintPayload>[] payload() default {};

    String lang();

    String script();

    @Target( { TYPE })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        ScriptAssert[] value();
    }
}

The Bean Validation API requires each constraint annotation to define the three attributes

  • message (for specifying the key used to resolve the error text in case of constraint violations)
  • groups (allowing the constraint to be assigned to validation groups, if required)
  • payload (not used by the Bean Validation API itself, but might be used by validation clients to assign custom payloads to a constraint)

Besides these obligatory attributes we define the two attributes

  • lang (used to specify the name of this constraint's script language as understood by the JSR 223 ScriptEngineManager)
  • script (used to specify the script to be evaluated).

Furthermore we specify, that the constraint shall only be allowed at type declarations (by using the @Target meta-annotation) and that the class ScriptAssertValidator shall be used to evaluate the constraint.

By defining an additional inner annotation @List, which takes an array of ScriptAsserts as value, we follow the JSR's recommendation to provide a way for placing multiple constraints of the same type at one object.

Implementing the constraint validator

Having defined the constraint annotation it's time to implement the accompanying validator class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ScriptAssertValidator implements
    ConstraintValidator<ScriptAssert, Object> {

    private String script;

    private String languageName;

    private ScriptEngineManager manager = new ScriptEngineManager();

    public void initialize(ScriptAssert constraintAnnotation) {

        this.script = constraintAnnotation.script();
        this.languageName = constraintAnnotation.lang();
    }

    public boolean isValid(Object value,
        ConstraintValidatorContext constraintValidatorContext) {

        ScriptEngine engine = manager.getEngineByName(languageName);

        if (engine == null) {
            throw new IllegalArgumentException(
                "No JSR 223 script engine found for language " + languageName);
        }

        engine.put("_this", value);

        try {
            return Boolean.TRUE.equals(engine.eval(script));
        } 
        catch (ScriptException e) {
            throw new RuntimeException(e);
        }
    }
}

In the initialize() method we store the given ScriptAssert annotation's values for the language name and the script contents.

The isValid() method is called by the Bean Validation runtime, whenever the constraint shall be evaluated. First we fetch a JSR 223 ScriptEngine for the given language. In order to make the object to be validated accessible inside the script, we put it into the engine's context under the name "_this". At last we call ScriptEngine's eval() method to evaluate the given script and check, whether the script's output equals to Boolean.TRUE.

Defining the error message

In case the constraint is violated, an error message is required to be put into the ConstraintViolation object created by the Bean Validation runtime. Error messages are defined in a resource file called ValidationMessages.properties (resp. localized derivations of that). So let's create that file with the following content:

1
de.gmorling.beanvalidation.scriptassert=Script statement "{script}" didn't evaluate to TRUE.

Testing the ScriptAssert annotation

Finally it's time to give our ScriptAssert constraint a little test run. Taking the CalendarEvent class from the introductory example, a test could look as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class CalendarEventTest {

    private static Validator validator;

    private static Date startDate;

    private static Date endDate;

    @BeforeClass
    public static void setUpValidatorAndDates() {

        validator = Validation.buildDefaultValidatorFactory().getValidator();

        startDate = new GregorianCalendar(2009, 8, 20).getTime();
        endDate = new GregorianCalendar(2009, 8, 21).getTime();
    }

    @Test
    public void validCalendarEvent() {

        CalendarEvent testEvent = 
            new CalendarEvent("Team meeting", startDate, endDate);

        assertTrue(validator.validate(testEvent).isEmpty());
    }

    @Test
    public void invalidCalendarEvent() {

        CalendarEvent testEvent = 
            new CalendarEvent("Team meeting", endDate, startDate);

        Set<ConstraintViolation<CalendarEvent>> constraintViolations = 
            validator.validate(testEvent);

        assertEquals(1, constraintViolations.size());
        assertEquals(
            "Script statement \"_this.startDate.before(_this.endDate)\" didn't evaluate to TRUE.",
            constraintViolations.iterator().next().getMessage());
    }

}

In validCalendarEvent() the event's start date is earlier than the end date, resulting in an empty set of constraint violations when validating the object. The opposite case is shown in invalidCalendarEvent(): We retrieve a ConstraintViolation as the event's start and end date are mixed up.

Note that as we are using Groovy as scripting language in the example, we need to have the Groovy library in the classpath (which already contains a JSR 223 compatible script engine implementation). Using Apache Maven, only the following dependency has to be added to your project's pom.xml:

1
2
3
4
5
6
7
8
...
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>1.6.4</version>
    <scope>test</scope>
</dependency>
...

Trying it out yourself

I put all the sources of this post into a little project over at GitHub. A binary can be found in my Maven repository. So if want to give it a test run, first add the repo to your pom.xml/settings.xml:

1
2
3
4
5
6
7
8
...
<repositories>
    <repository>
        <id>http://gunnarmorling-maven-repo.googlecode.com/svn/repo/</id>
        <url>http://gunnarmorling-maven-repo.googlecode.com/svn/repo/</url>
    </repository>
</repositories>
...

Then simply add the following dependency to your POM:

1
2
3
4
5
6
7
...
<dependency>
    <groupId>de.gmorling</groupId>
    <artifactId>script-assert</artifactId>
    <version>0.2</version>
</dependency>
...

Furthermore you need the (runtime) dependency to a Bean Validation implementation (the script-assert project only has a dependency to the API). I recommend to take the reference implementation, so add a dependency to the RI itself as well as a binding for sl4j (used for logging purposes):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.0.0.Beta3</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.5.6</version>
    <scope>runtime</scope>
</dependency>
...

As always any feedback on the usefulness of this post and any ideas for further improvement would be highly appreciated.

Monday, February 16, 2009

Bookmark and Share

DBUnit is a great tool when it comes to the management of test data. When doing data driven unit tests, database tables have to be populated with predefined records, that can be accessed by the code under test. DBUnit allows the specification of such test records by the means of data set files as in the following example:

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <location num="6" addr="Webster Street" date="2009-02-16"/>
</dataset>

The data that can be inserted with such data set files is pretty static by nature. DBUnit offers a bit of dynamic with its ReplacementDataSet, which allows to replace custom-defined placeholders (e.g. [SYSDATE]) with other values (e.g. new Date()). But I am aware of no possibility to use more flexible expressions to insert things like "now - 14 days" into a date field for instance.

So I set out to create a data set implementation, that allows to use scripting language expressions in its fields. Using this ScriptableDataSet, data set files like the following can be loaded and processed:

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <location num="jruby:12/2" addr="jruby:'Webster Street'.reverse" date="jruby:DateTime::now() - 14"/>
</dataset>

Having the power of a scripting language at your hands any dynamic expressions thinkable can be used in the fields of a data set – also "now - 14 days" is no problem any more.

How does it work? The scriptable data set leverages the scripting language integration API as defined by JSR 223 ("Scripting for the Java Platform"). An implementation of that API is part of Java 6, but it generally can be used with previous Java versions as well.

To use a certain language in a data set, a JSR 223 compatible engine for that language has to be added to the class path. Loads of such engines can be found at the scripting project at java.net. Having added the engine for your preferred scripting language (or by using the Rhino engine for JavaScript, which is delivered with the Java 6 JDK), a ScriptableDataSet can be created as follows:

1
2
3
4
5
6
7
8
List<Class<? extends ScriptInvocationHandler>> handlers = 
    new ArrayList<Class<? extends ScriptInvocationHandler>>();
handlers.add(TestInvocationHandler.class);

IDataSet dataSet = 
    new ScriptableDataSet(
        new FlatXmlDataSet(new File("dataset.xml")),
        new ScriptableDataSetConfig("jruby", "jruby:", handlers));

Sporting DBUnit's decorator scheme, a ScriptableDataSet is created wrapping another IDataSet. Additionally a ScriptableDataSetConfig object has to be provided, that specifies

  • the name of the scripting language to be used as understood by the JSR 223 ScriptEngineManager ("jruby")
  • a prefix that shall precede all fields containing scripts in that language ("jruby:")
  • an optional list of ScriptInvocationHandlers, that can be used to pre-process (e.g. to add common imports) and post-process scripts (e.g. to convert results into data types understood by DBUnit)

So if you like the idea of the scriptable data set, don't hesistate and get your hands on it at its git repo over at github. Do you think, it is useful at all, and if so, which scenarios for its usage could you think of?

Saturday, February 14, 2009

Bookmark and Share

One of the features new to Java 6 is its built-in support for scripting languages (an introductory article on that topic can be found here), which enables you to load and execute programs written in a scripting language directly from within your Java program.

The scripting support in Java 6 is realized by providing an implementation of JSR 223 ("Scripting for the Java Platform"). While JavaScript is directly supported by the JDK itself, any other scripting language can be integrated as well by simply adding a JSR 223 compatible scripting engine to the class path.

The first place to look for JSR 223 scripting engines is https://scripting.dev.java.net/, where engines for Ruby, Python, Groovy and a lot of other languages can be found.

To give it a try, I wanted to include the JRuby engine into a Maven based project. That engine can be found in the Maven repo at java.net, but unfortunetaly its pom.xml is somewhat defective, as it contains the dependency script-api:javax.script, which neither exists in the java.net repository nor any other one I am aware of.

But when running on Java 6, it isn't required at all – as the scripting API is part of the JDK. So I excluded the dependency in the pom.xml of my own project, which reads as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<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>org.gm</groupId>
    <artifactId>jruby-scripting</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>jruby-scripting</name>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jruby</groupId>
            <artifactId>jruby</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.sun.script.jruby</groupId>
            <artifactId>jruby-engine</artifactId>
            <version>1.1.6</version>
            <exclusions>
                <exclusion>
                    <groupId>script-api</groupId>
                    <artifactId>javax.script</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>maven2-repository.dev.java.net</id>
            <name>Java.net Repository for Maven 2</name>
            <url>http://download.java.net/maven/2/</url>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>RELEASE</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Having fixed that, we can try the scripting API by evaluating a simple Ruby script, that receives a variable provided by the hosting Java program and returns the obligatory String "Hello, jruby!" ;-):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.gm.jrubyscripting;

import static org.junit.Assert.*;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import org.junit.Test;

public class HelloJRuby {

    @Test
    public void helloJRuby() throws Exception {

        String engineName = "jruby";

        ScriptEngineManager manager = new ScriptEngineManager();

        ScriptEngine jRubyEngine = manager.getEngineByName(engineName);
        assertNotNull(jRubyEngine);

        jRubyEngine.put("engine", engineName);

        assertEquals(
            "Hello, jruby!",
            jRubyEngine.eval("return 'Hello, ' + $engine + '!'"));
    }
}

But what to do, if you are not running on Java 6? After some more searching I finally managed to find the missing dependency in the repo of the Mule project, but with groupId and artifactId interchanged.

From there it can be added to the project, for example using a separate Maven build profile to be activated on JDK versions < 1.6:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
<profiles>
    <profile>
        <activation>
            <jdk>[1.3,1.6)</jdk>
        </activation> 
        <dependencies>
            <dependency>
                <groupId>javax.script</groupId>
                <artifactId>script-api</artifactId>
                <version>1.0</version>
            </dependency>
        </dependencies>
        <repositories>
            <repository>
                <id>dist.codehaus.org/mule</id>
                <name>Mule Repository for Maven 2</name>
                <url>http://dist.codehaus.org/mule/dependencies/maven2/</url>
            </repository>
        </repositories>
    </profile>
</profiles>
...