Skip to content

Instantly share code, notes, and snippets.

@dteleguin
Last active March 29, 2016 03:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dteleguin/c93fe4a4c666234729d8 to your computer and use it in GitHub Desktop.
Save dteleguin/c93fe4a4c666234729d8 to your computer and use it in GitHub Desktop.

Preface

For Java EE developers, JavaScript has been around for years. Introduced in 2012, Nashorn is a modern and performant JavaScript engine for JVM. JavaScript is a perfect candidate for being first-class citizen of the Java EE ecosystem. What prevents it from being one is lack of integration with CDI - a glue that binds together application components and wires the application to Java EE platform resources.

The goal of the project is to provide such an integration, together with several other productivity features.

Initially, it was inspired by Grails Console.

Examples

Print banner upon application startup. It is assumed that the @Default Configuration bean is provided by the application:

/**
@import org.apache.commons.configuration.Configuration
@import java.util.logging.Level

@Startup(order = 1)
@Inject Configuration config
*/

$LOG.log(Level.INFO, "Starting MyApplication {0} b{1}", [
        config.getString("myapp.version"),
        config.getString("myapp.build")
    ]);

Run FlyWay migrations:

/**
@import javax.sql.DataSource
@import org.flywaydb.core.Flyway

@Startup(order = 2)
@Resource(lookup = "jdbc/MyDB") DataSource ds
*/

var flyway = new Flyway();
flyway.setDataSource(ds);
flyway.setBaselineOnMigrate(true);
flyway.migrate();

Do periodic housekeeping:

/**
@Name("Housekeeping")
@Description("Do some housekeeping once a day")
@Schedule("02 4 * * *")

@import my.package.HouseKeeping

@Inject HouseKeeping hk
*/

hk.doHousekeeping();

Log HTTP session creation:

/**
@Name("SessionLog")
@Observes @Initialized(SessionScoped.class) HttpSession session

@import my.package.log.HTTPSessionLogRecord
@import javax.persistence.EntityManager
@import javax.transaction.UserTransaction

@PersistenceContext EntityManager em
@Resource UserTransaction tx
*/

tx.begin();
var record = new HTTPSessionLogRecord(session);
em.persist(record);
tx.commit();

Features

CDI Support

In addition to @Inject, the following Java EE resource injections are supported:

  • @Resource
  • @EJB
  • @PersistenceUnit / @PersistenceContext
  • @WebServiceRef

The syntax generally mirrors that of Java code. Injections are defined inside a script header, JSDoc-style.

Execution Modes

Scripts can be invoked in the following ways:

  1. Programmatic (using Java or REST APIs)
  2. Interactive (using web console or IDE plugin)
  3. On startup (using the @Startup annotation)
  4. Scheduled/periodic (using the @Schedule annotation)
  5. Triggered by CDI event (using the @Observes annotation)

Script Libraries

The runtime will look for scripts in the following sources:

  • Application library: a read-only script collection that is packaged and deployed together with the application
  • User library: a read-write collection that resides in a filesystem.

Miscellaneous

Imports

A typical server-side script usually begins with a number of surrogate "imports" like this:

var Foo = my.package.Foo;
var Bar = my.package.Bar;
var Configuration = org.apache.commons.configuration.Configuration;
var Level = java.util.logging.Level;
...
var foo = new Foo();

Now one can easily get rid of such a surrogates by using @import directives in a script header. Wildcards and static imports are supported.

Logging

The runtime provides an implicit $LOG variable that is initialized with a java.util.logging.Logger instance. The logger name is composed of a common prefix + (unique) script name.

Use cases

Application startup

A typical Java EE application's initialization process is usually comprised of the following steps:

  1. Print a banner with application name, version and build ID/timestamp;
  2. Run Flyway/LiquiBase migrations;
  3. Generate test data (if on a dev or QA box);
  4. Read and validate configuration;
  5. Create working directories if they do not exist;
  6. Set up logging system (e.g., instruct JUL to use custom log directory);
  7. Configure JAX-RS multipart handling;
  8. etc.

Nearly all of these could be implemented as scripts, with the clear execution order and ability to enable/disable some of them without recompilation/redeployment. For example, a developer might intentionally disable database migrations on his box.

Generating test data is a particular use case. Modern development approaches assume rapidly changing schema and/or using NoSQL databases. In such cases, generating test data from application code is much more convenient than shipping tons of pre-generated SQL or JSON data. Additionally, test data parameteres could be changed on the fly, without application recompilation. Producing a completely different test data set would be as simlpe as tweaking a script and reloading your application.

Developer productivity

Real-life data is usually orders of magnitude more complex than any test data imaginable.

Simply open web console on a live application and:

  • isolate a bug that manifests in production only, without connecting debugger to application server and without suspending application threads;
  • see how a particular JPQL query performs on live data;
  • try new data processing algorithms and other ideas;
  • etc.

Of course running arbitrary code on a production system is a double-edged sword. One should fully realize the risks of potentially corrupting production data by running untested code.

Maintenance & Monitoring

If you need to quickly provide application specific metrics, and consider JMX a sledge-hammer, you can use server-side scripts as a poor man's alternative. Similarly, if there are some maintenance tasks to be done on occasion, implement them as scripts, so even a non-programmer could invoke them simply by selecting from a list.

Fluid Logic

The Fluid Logic pattern is described in Adam Bien's Real World Java EE Patterns book:

Java is a static language with emphasis on its strong typing. Java is well suitable for the implementation of well-defned, stable business logic. Algorithms that often change require recompilation and even redeployment of the whole application.

Strong typing and the static nature of Java are features and drawbacks at the same time. For integration purposes, dynamic languages such as JavaScript, Ruby, or Python are better suitable and more effcient. With Java it is hardly possible to evaluate or interpret business logic at runtime.

With CDI injections available, Fluid Logic can be implemented in a more natural way and with less boilerplate code. All the considerations on testing, performance, security and robustness apply; refer to the corresponding book chapter for further info.

APIs and Interfaces

Java API

REST API

Interactive Console

IDE Plugins

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment