Skip to content

Commit

Permalink
Merge pull request #209 from RedSeal-co/add-ensureJvm-registerClient-api
Browse files Browse the repository at this point in the history
Add the registerClient/ensureJvm API
  • Loading branch information
joeferner committed Apr 18, 2015
2 parents 1904f7b + d1e8172 commit fc723a6
Show file tree
Hide file tree
Showing 11 changed files with 477 additions and 21 deletions.
32 changes: 31 additions & 1 deletion README.md
Expand Up @@ -179,7 +179,7 @@ java.newInstancePromise("java.util.ArrayList")
* If you provide `asyncOptions.promisify` then you must provide a *non-empty* string for `asyncOptions.promiseSuffix`.
* Either (but not both) `asyncSuffix` or `syncSuffix` can be the empty string. If you want the defacto standard behavior for no suffix on async methods, you must provide an empty string for `asyncSuffix`.
* We've tested promises with five Promises/A+ implementations. See `testHelpers.js` for more information.
* NOTE: Due to specifics of initialization order, the methods `java.newInstancePromise`, `java.callMethodPromise`, and `java.callStaticMethodPromise` are not available until some other java method is called. You may need to call some other java method such as `java.import()` to finalize java initialization.
* NOTE: Due to specifics of initialization order, the methods `java.newInstancePromise`, `java.callMethodPromise`, and `java.callStaticMethodPromise` are not available until the JVM has been created. You may need to call some other java method such as `java.import()` to finalize java initialization, or even better, the function `java.ensureJVM()`.
##### Special note about the exported module functions `newInstance`, `callMethod`, and `callStaticMethod`.
These methods come in both async and sync variants. If you provide the `promisify` and `promiseSuffix` attributes in asyncOptions then you'll also get the Promises/A+ variant for these three functions. However, if you change the defacto
Expand All @@ -201,6 +201,10 @@ In most cases it is still acceptable to use `java.newArray()`. But it is now pos
Note that when passing a Javascript array (e.g. `['a', 'b', 'c']`) for a varargs parameter, node-java must infer the Java type of the array. If all of the elements are of the same javascript primitive type (`string` in this example) then node-java will create a Java array of the corresponding type (e.g. `java.lang.String`). The Java types that node-java can infer are: `java.lang.String`, `java.lang.Boolean`, `java.lang.Integer`, `java.lang.Long`, and `java.lang.Double`. If an array has a mix of `Integer`, `Long`, and `Double`, then the inferred type will be `java.lang.Number`. Any other mix will result in an inferred type of `java.lang.Object`.
## JVM Creation
With v0.5.1 a new API is available to make it easier for a complex application to have full control over JVM creation. In particular, it is now easier to compose an application from several modules, each of which must add to the Java classpath and possibly do other operations just before or just after the JVM has been created. See the methods [ensureJvm](#javaEnsureJvm) and [registerClient](#javaRegisterClient). See also several of the tests in the testAsyncOptions directory.
# Release Notes
### v0.5.0
Expand Down Expand Up @@ -233,6 +237,10 @@ Note that when passing a Javascript array (e.g. `['a', 'b', 'c']`) for a varargs
* [newDouble](#javaNewDouble)
* [newFloat](#javaNewFloat)
* [newProxy](#javaNewProxy)
* [isJvmCreated](#javaIsJvmCreated)
* [registerClient](#javaRegisterClient)
* [registerClientP](#javaRegisterClientP)
* [ensureJvm](#javaEnsureJvm)
## java objects
* [Call Method](#javaObjectCallMethod)
Expand Down Expand Up @@ -520,6 +528,28 @@ __Example__
var thread = java.newInstanceSync("java.lang.Thread", myProxy);
thread.start();
<a name="javaisJvmCreated" />
**java.isJvmCreated()**
Returns true if the JVM has been created. The JVM can only be created once.
<a name="javaRegisterClient" />
**java.registerClient(before, after)**
Register that a client wants to be called back immediately before and/or immediately after the JVM is created. If used, this function must be called before the JVM has been created. The before function is typically used to add to the classpath. The function may execute asynchronous operations (such as a async glob function). The after function is sometimes useful for doing one-time initialization that requires the JVM to first be initialized. If either function is unnecessary, use `null` or `undefined`. See also `registerClientP` and `ensureJvm`. See the unit tests in `testAsyncOptions` for examples.
<a name="javaRegisterClientP" />
**java.registerClientP(before, after)**
Like java.registerClient, but before and after are assumed to be functions returning promises.
<a name="javaEnsureJvm" />
**java.ensureJvm(callback)**
If the JVM has not yet been created, execute the full JVM initialization process, then call callback function when initialization is complete. If the JVM has been created, just call the callback. Note that the full initialization process includes: 1) executing all registered client *before* hooks, 2) creating the JVM, then 3) executing all registered client *after* hooks.
<a name="javaObject"/>
## java object
Expand Down
121 changes: 121 additions & 0 deletions lib/nodeJavaBridge.js
Expand Up @@ -2,6 +2,8 @@

process.env.PATH += require('../build/jvm_dll_path.json');

var _ = require('lodash');
var async = require('async');
var path = require('path');
var fs = require('fs');
var binaryPath = path.resolve(path.join(__dirname, "../build/Release/nodejavabridge_bindings.node"));
Expand Down Expand Up @@ -30,6 +32,125 @@ var SyncCall = function(obj, method) {
throw new Error('Sync method not found:' + syncMethodName);
}

java.isJvmCreated = function() {
return typeof java.onJvmCreated !== 'function';
}

var clients = [];

// We provide two methods for 'clients' of node-java to 'register' their use of java.
// By registering, a client gets the opportunity to be called asynchronously just before the JVM is created,
// and just after the JVM is created. The before hook function will typically be used to add to java.classpath.
// The function may peform asynchronous operations, such as async [glob](https://github.com/isaacs/node-glob)
// resolutions of wild-carded file system paths, and then notify when it has finished via either calling
// a node-style callback function, or by resolving a promise.

// A client can register function hooks to be called before and after the JVM is created.
// If the client doesn't need to be called back for either function, it can pass null or undefined.
// Both before and after here are assumed to be functions that accept one argument that is a node-callback function.
java.registerClient = function(before, after) {
if (java.isJvmCreated()) {
throw new Error('java.registerClient() called after JVM already created.');
}
clients.push({before: before, after: after});
}

// A client can register function hooks to be called before and after the JVM is created.
// If the client doesn't need to be called back for either function, it can pass null or undefined.
// Both before and after here are assumed to be functions that return Promises/A+ `thenable` objects.
java.registerClientP = function(beforeP, afterP) {
if (java.isJvmCreated()) {
throw new Error('java.registerClient() called after JVM already created.');
}
clients.push({beforeP: beforeP, afterP: afterP});
}

function runBeforeHooks(done) {
function iterator(client, cb) {
try {
if (client.before) {
client.before(cb);
}
else if (client.beforeP) {
client.beforeP().then(function(ignored) { cb(); }, function(err) { cb(err); });
}
else {
cb();
}
}
catch (err) {
cb(err);
}
}
async.each(clients, iterator, done);
}

function createJVMAsync(callback) {
var ignore = java.newLong(0); // called just for the side effect that it will create the JVM
callback();
}

function runAfterHooks(done) {
function iterator(client, cb) {
try {
if (client.after) {
client.after(cb);
}
else if (client.afterP) {
client.afterP().then(function(ignored) { cb(); }, function(err) { cb(err); });
}
else {
cb();
}
}
catch (err) {
cb(err);
}
}
async.each(clients, iterator, done);
}

function initializeAll(done) {
async.series([runBeforeHooks, createJVMAsync, runAfterHooks], done);
}

// This function ensures that the JVM has been launched, asynchronously. The application can be notified
// when the JVM is fully created via either a node callback function, or via a promise.
// If the parameter `callback` is provided, it is assume be a node callback function.
// If the parameter is not provided, and java.asyncOptions.promisify has been specified,
// then this function will return a promise, by promisifying itself and then calling that
// promisified function.
// This function may be called multiple times -- the 2nd and subsequent calls are no-ops.
// However, once this method has been called (or the JVM is launched as a side effect of calling other java
// methods), then clients can no longer use the registerClient API.
java.ensureJvm = function(callback) {

// First see if the promise-style API should be used.
// This must be done first in order to ensure the proper API is used.
if (_.isUndefined(callback) && java.asyncOptions && _.isFunction(java.asyncOptions.promisify)) {
// Create a promisified version of this function.
var launchJvmPromise = java.asyncOptions.promisify(java.ensureJvm.bind(java));
// Call the promisified function, returning its result, which should be a promise.
return launchJvmPromise();
}

// If we get here, callback must be a node-style callback function. If not, throw an error.
else if (!_.isFunction(callback)) {
throw new Error('java.launchJvm(cb) requires its one argument to be a callback function.');
}

// Now check if the JVM has already been created. If so, we assume that the jvm was already successfully
// launched, and we can just implement idempotent behavior, i.e. silently notify that the JVM has been created.
else if (java.isJvmCreated()) {
return setImmediate(callback);
}

// Finally, queue the initializeAll function.
else {
return setImmediate(initializeAll, callback);
}
}

java.onJvmCreated = function() {
if (java.asyncOptions) {
syncSuffix = java.asyncOptions.syncSuffix;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -26,12 +26,12 @@
"url": "https://github.com/joeferner/node-java.git"
},
"dependencies": {
"async": "0.9.0",
"find-java-home": "0.1.2",
"glob": "5.0.5",
"nan": "1.7.0"
},
"devDependencies": {
"async": "0.9.0",
"chalk": "1.0.0",
"lodash": "3.7.0",
"nodeunit": "0.9.1",
Expand Down
33 changes: 33 additions & 0 deletions testAsyncOptions/testClientBeforeError.js
@@ -0,0 +1,33 @@
// testClientBeforeError.js

var _ = require('lodash');
var java = require("../");
var nodeunit = require("nodeunit");

module.exports = {

clientBeforeError: function(test) {
test.expect(6);
test.ok(!java.isJvmCreated());

java.asyncOptions = {
syncSuffix: "Sync",
};

function before(callback) {
test.ok(!java.isJvmCreated());
callback(new Error('dummy error'));
}

java.registerClient(before);

java.ensureJvm(function(err) {
test.ok(_.isObject(err));
test.ok(err instanceof Error);
test.strictEqual(err.message, 'dummy error');
test.ok(!java.isJvmCreated());
test.done();
});
}

}
33 changes: 33 additions & 0 deletions testAsyncOptions/testClientBeforeThrows.js
@@ -0,0 +1,33 @@
// testClientBeforeThrows.js

var _ = require('lodash');
var java = require("../");
var nodeunit = require("nodeunit");

module.exports = {

clientBeforeThrows: function(test) {
test.expect(6);
test.ok(!java.isJvmCreated());

java.asyncOptions = {
syncSuffix: "Sync",
};

function before(callback) {
test.ok(!java.isJvmCreated());
throw new Error('dummy error');
}

java.registerClient(before);

java.ensureJvm(function(err) {
test.ok(_.isObject(err));
test.ok(err instanceof Error);
test.strictEqual(err.message, 'dummy error');
test.ok(!java.isJvmCreated());
test.done();
});
}

}
44 changes: 44 additions & 0 deletions testAsyncOptions/testClientPBeforeError.js
@@ -0,0 +1,44 @@
// testClientPBeforeError.js

var _ = require('lodash');
var java = require("../");
var nodeunit = require("nodeunit");
var when = require('when');

module.exports = {

clientPBeforeError: function(test) {
test.expect(6);
test.ok(!java.isJvmCreated());

java.asyncOptions = {
syncSuffix: "Sync",
promiseSuffix: 'Promise',
promisify: require('when/node').lift // https://github.com/cujojs/when
};

function beforeP() {
var promise = when.promise(function(resolve, reject) {
test.ok(!java.isJvmCreated());
reject(new Error('dummy error'));
});
return promise;
}

java.registerClientP(beforeP);

java.ensureJvm().done(
function () {
test.ok(false);
},
function(err) {
test.ok(_.isObject(err));
test.ok(err instanceof Error);
test.strictEqual(err.message, 'dummy error');
test.ok(!java.isJvmCreated());
test.done();
}
);
}

}
44 changes: 44 additions & 0 deletions testAsyncOptions/testClientPBeforeThrows.js
@@ -0,0 +1,44 @@
// testClientPBeforeThrows.js

var _ = require('lodash');
var java = require("../");
var nodeunit = require("nodeunit");
var when = require('when');

module.exports = {

clientPBeforeThrows: function(test) {
test.expect(6);
test.ok(!java.isJvmCreated());

java.asyncOptions = {
syncSuffix: "Sync",
promiseSuffix: 'Promise',
promisify: require('when/node').lift // https://github.com/cujojs/when
};

function beforeP() {
var promise = when.promise(function(resolve, reject) {
test.ok(!java.isJvmCreated());
throw new Error('dummy error');
});
return promise;
}

java.registerClientP(beforeP);

java.ensureJvm().done(
function () {
test.ok(false);
},
function(err) {
test.ok(_.isObject(err));
test.ok(err instanceof Error);
test.strictEqual(err.message, 'dummy error');
test.ok(!java.isJvmCreated());
test.done();
}
);
}

}

0 comments on commit fc723a6

Please sign in to comment.