package io.vertx.lang.ruby;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import org.jruby.CompatVersion;
import org.jruby.RubyClass;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyModule;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.ScriptingContainer;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
class ContainerHolder {
private static final AtomicInteger seq = new AtomicInteger();
private final JRubyVerticleFactory factory;
private final String verticleName;
private ScriptingContainer container;
private int refs;
ContainerHolder(JRubyVerticleFactory factory, String verticleName) {
this.factory = factory;
this.verticleName = verticleName;
}
String getVerticleName() {
return verticleName;
}
synchronized Deployment create(String gemPath, Vertx vertx, ClassLoader classLoader, Future<?> startFuture) {
if (refs++ == 0) {
ScriptingContainer cont = new ScriptingContainer(LocalContextScope.SINGLETHREAD);
RubyInstanceConfig config = cont.getProvider().getRubyInstanceConfig();
if (gemPath != null) {
Map newEnv = new HashMap(config.getEnvironment());
newEnv.put("GEM_PATH", gemPath);
config.setEnvironment(newEnv);
}
if (classLoader instanceof URLClassLoader) {
config.setLoadPaths(Arrays.stream(((URLClassLoader) classLoader).getURLs())
.map(u -> {
String form = u.toExternalForm();
if (form.endsWith(".jar")) {
return "jar:" + form + "!";
} else {
return form;
}
}).collect(Collectors.toList()));
}
cont.setClassLoader(classLoader);
cont.setError(new PrintStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
}
}));
cont.put("$_vertx", vertx);
cont.runScriptlet("require 'vertx/vertx'");
cont.runScriptlet("require 'vertx/future'");
cont.runScriptlet("$vertx=Vertx::Vertx.new($_vertx)");
cont.remove("$_vertx");
container = cont;
}
try {
String modName = "Mod___VertxInternalVert__" + seq.incrementAndGet();
StringBuilder script = new StringBuilder("require 'vertx/util/vertx_require'\n").append("module ").append(modName).append(";extend self;");
URL url = classLoader.getResource(verticleName);
if (url == null) {
File f = new File(verticleName);
if (!f.isAbsolute()) {
f = new File(System.getProperty("user.dir"), verticleName);
}
if (f.exists() && f.isFile()) {
url = f.toURI().toURL();
}
}
if (url == null) {
throw new IllegalStateException("Cannot find verticle script: " + verticleName + " on classpath");
}
int idx = verticleName.lastIndexOf('/');
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
for (String line = br.readLine(); line != null; line = br.readLine()) {
script.append(line).append("\n");
}
br.close();
script.append(";end;").append(modName);
RubyModule wrappingModule = (RubyModule) container.runScriptlet(new StringReader(script.toString()), verticleName.substring(idx + 1));
if (wrappingModule.getMethods().containsKey("vertx_start")) {
container.callMethod(wrappingModule, "vertx_start");
startFuture.complete();
} else if (wrappingModule.getMethods().containsKey("vertx_start_async")) {
invokeAsync(wrappingModule, "vertx_start_async", startFuture);
} else {
startFuture.complete();
}
return new Deployment(modName, wrappingModule);
} catch (Throwable t) {
startFuture.fail(t);
return null;
}
}
synchronized void undeploy(Deployment deployment, Future<?> stopFuture) {
Future<Void> fut = Future.future();
fut.setHandler(ar -> {
container.runScriptlet("Object.send(:remove_const, :" + deployment.modName + ")");
if (--refs == 0) {
factory.removeVerticle(this);
container.terminate();
}
if (ar.succeeded()) {
stopFuture.complete();
} else {
stopFuture.fail(ar.cause());
}
});
if (deployment.wrappingModule.getMethods().containsKey("vertx_stop")) {
container.callMethod(deployment.wrappingModule, "vertx_stop");
fut.complete();
} else if (deployment.wrappingModule.getMethods().containsKey("vertx_stop_async")) {
invokeAsync(deployment.wrappingModule, "vertx_stop_async", fut);
} else {
fut.complete();
}
}
private void invokeAsync(RubyModule module, String name, Future future) {
org.jruby.RubyClass rubyClass = (RubyClass) container.runScriptlet("return ::Vertx::Future");
Object wrappedFuture = container.callMethod(rubyClass, "new", future);
container.callMethod(module, name, wrappedFuture);
}
}