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.
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();
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.
Scripts can be invoked in the following ways:
- Programmatic (using Java or REST APIs)
- Interactive (using web console or IDE plugin)
- On startup (using the
@Startup
annotation) - Scheduled/periodic (using the
@Schedule
annotation) - Triggered by CDI event (using the
@Observes
annotation)
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.
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.
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.
A typical Java EE application's initialization process is usually comprised of the following steps:
- Print a banner with application name, version and build ID/timestamp;
- Run Flyway/LiquiBase migrations;
- Generate test data (if on a dev or QA box);
- Read and validate configuration;
- Create working directories if they do not exist;
- Set up logging system (e.g., instruct JUL to use custom log directory);
- Configure JAX-RS multipart handling;
- 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.
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.
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.
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.