diff --git a/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyActivator.java b/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyActivator.java
index 0e5cd9a..3fc78f5 100644
--- a/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyActivator.java
+++ b/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyActivator.java
@@ -16,9 +16,13 @@
package com.ning.billing.osgi.bundles.jruby;
+import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
import org.jruby.embed.ScriptingContainer;
import org.osgi.framework.BundleContext;
@@ -34,21 +38,24 @@ import com.google.common.base.Objects;
public class JRubyActivator extends KillbillActivatorBase {
- private static final String jrubyPluginsConfDir = System.getProperty("com.ning.billing.osgi.bundles.jruby.conf.dir");
+ private static final String JRUBY_PLUGINS_CONF_DIR = System.getProperty("com.ning.billing.osgi.bundles.jruby.conf.dir");
+ private static final int JRUBY_PLUGINS_RESTART_DELAY_SECS = Integer.parseInt(System.getProperty("com.ning.billing.osgi.bundles.jruby.restart.delay.secs", "5"));
+
+ private static final String TMP_DIR_NAME = "tmp";
+ private static final String RESTART_FILE_NAME = "restart.txt";
private JRubyPlugin plugin = null;
+ private ScheduledFuture<?> restartFuture = null;
- private final static String KILLBILL_PLUGIN_JPAYMENT = "Killbill::Plugin::JPayment";
- private final static String KILLBILL_PLUGIN_JNOTIFICATION = "Killbill::Plugin::JNotification";
+ private static final String KILLBILL_PLUGIN_JPAYMENT = "Killbill::Plugin::JPayment";
+ private static final String KILLBILL_PLUGIN_JNOTIFICATION = "Killbill::Plugin::JNotification";
public void start(final BundleContext context) throws Exception {
-
super.start(context);
withContextClassLoader(new PluginCall() {
@Override
public void doCall() {
-
logService.log(LogService.LOG_INFO, "JRuby bundle activated");
// Retrieve the plugin config
@@ -69,19 +76,57 @@ public class JRubyActivator extends KillbillActivatorBase {
}
// Validate and instantiate the plugin
+ startPlugin(rubyConfig, pluginMain, context);
+ }
+ }, this.getClass().getClassLoader());
+ }
- final Map<String, Object> killbillServices = retrieveKillbillApis(context);
- killbillServices.put("root", rubyConfig.getPluginVersionRoot().getAbsolutePath());
- killbillServices.put("logger", logService);
- // Default to the plugin root dir if no jruby plugins specific configuration directory was specified
- killbillServices.put("conf_dir", Objects.firstNonNull(jrubyPluginsConfDir, rubyConfig.getPluginVersionRoot().getAbsolutePath()));
- plugin.instantiatePlugin(killbillServices, pluginMain);
+ private void startPlugin(final PluginRubyConfig rubyConfig, final String pluginMain, final BundleContext context) {
+ final Map<String, Object> killbillServices = retrieveKillbillApis(context);
+ killbillServices.put("root", rubyConfig.getPluginVersionRoot().getAbsolutePath());
+ killbillServices.put("logger", logService);
+ // Default to the plugin root dir if no jruby plugins specific configuration directory was specified
+ killbillServices.put("conf_dir", Objects.firstNonNull(JRUBY_PLUGINS_CONF_DIR, rubyConfig.getPluginVersionRoot().getAbsolutePath()));
+
+ // Initial start
+ doStartPlugin(pluginMain, context, killbillServices);
+
+ // Setup the restart mechanism. This is useful for hotswapping plugin code
+ // The principle is similar to the one in Phusion Passenger:
+ // http://www.modrails.com/documentation/Users%20guide%20Apache.html#_redeploying_restarting_the_ruby_on_rails_application
+ final File tmpDirPath = new File(rubyConfig.getPluginVersionRoot().getAbsolutePath() + "/" + TMP_DIR_NAME);
+ if (!tmpDirPath.exists()) {
+ if (!tmpDirPath.mkdir()) {
+ logService.log(LogService.LOG_WARNING, "Unable to create directory " + tmpDirPath + ", the restart mechanism is disabled");
+ return;
+ }
+ }
+ if (!tmpDirPath.isDirectory()) {
+ logService.log(LogService.LOG_WARNING, tmpDirPath + " is not a directory, the restart mechanism is disabled");
+ return;
+ }
- logService.log(LogService.LOG_INFO, "Starting JRuby plugin " + plugin.getPluginMainClass());
- plugin.startPlugin(context);
+ // TODO Switch to failsafe once in killbill-commons
+ restartFuture = Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(new Runnable() {
+ long lastRestartMillis = System.currentTimeMillis();
+ @Override
+ public void run() {
+ final File restartFile = new File(tmpDirPath + "/" + RESTART_FILE_NAME);
+ if (!restartFile.isFile()) {
+ return;
+ }
+
+ if (restartFile.lastModified() > lastRestartMillis) {
+ logService.log(LogService.LOG_INFO, "Restarting JRuby plugin " + plugin.getPluginMainClass());
+
+ doStopPlugin(context);
+ doStartPlugin(pluginMain, context, killbillServices);
+
+ lastRestartMillis = restartFile.lastModified();
+ }
}
- }, this.getClass().getClassLoader());
+ }, JRUBY_PLUGINS_RESTART_DELAY_SECS, JRUBY_PLUGINS_RESTART_DELAY_SECS, TimeUnit.SECONDS);
}
private PluginRubyConfig retrievePluginRubyConfig(final BundleContext context) {
@@ -103,13 +148,24 @@ public class JRubyActivator extends KillbillActivatorBase {
withContextClassLoader(new PluginCall() {
@Override
public void doCall() {
- plugin.stopPlugin(context);
+ restartFuture.cancel(true);
+ doStopPlugin(context);
killbillAPI.close();
logService.close();
}
}, this.getClass().getClassLoader());
}
+ private void doStartPlugin(final String pluginMain, final BundleContext context, final Map<String, Object> killbillServices) {
+ logService.log(LogService.LOG_INFO, "Starting JRuby plugin " + plugin.getPluginMainClass());
+ plugin.instantiatePlugin(killbillServices, pluginMain);
+ plugin.startPlugin(context);
+ }
+
+ private void doStopPlugin(final BundleContext context) {
+ plugin.stopPlugin(context);
+ }
+
// We make the explicit registration in the start method by hand as this would be called too early
// (see OSGIKillbillEventDispatcher)
@Override
@@ -145,6 +201,7 @@ public class JRubyActivator extends KillbillActivatorBase {
private static interface PluginCall {
+
public void doCall();
}