package org.embulk.jruby;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import org.slf4j.ILoggerFactory;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
import com.google.inject.Binder;
import com.google.inject.Scopes;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ProviderWithDependencies;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.ScriptingContainer;
import org.embulk.plugin.PluginSource;
import org.embulk.config.ConfigSource;
import org.embulk.config.ModelManager;
import org.embulk.exec.ForSystemConfig;
import org.embulk.spi.BufferAllocator;
public class JRubyScriptingModule
implements Module
{
public JRubyScriptingModule(ConfigSource systemConfig)
{
}
@Override
public void configure(Binder binder)
{
binder.bind(ScriptingContainer.class).toProvider(ScriptingContainerProvider.class).in(Scopes.SINGLETON);
Multibinder<PluginSource> multibinder = Multibinder.newSetBinder(binder, PluginSource.class);
multibinder.addBinding().to(JRubyPluginSource.class);
}
private static class ScriptingContainerProvider
implements ProviderWithDependencies<ScriptingContainer>
{
private final Injector injector;
private final boolean useGlobalRubyRuntime;
private final String gemHome;
@Inject
public ScriptingContainerProvider(Injector injector, @ForSystemConfig ConfigSource systemConfig)
{
this.injector = injector;
// use_global_ruby_runtime is valid only when it's guaranteed that just one Injector is
// instantiated in this JVM.
this.useGlobalRubyRuntime = systemConfig.get(boolean.class, "use_global_ruby_runtime", false);
this.gemHome = systemConfig.get(String.class, "gem_home", null);
// TODO get jruby-home from systemConfig to call jruby.container.setHomeDirectory
// TODO get jruby-load-paths from systemConfig to call jruby.container.setLoadPaths
}
public ScriptingContainer get()
{
LocalContextScope scope = (useGlobalRubyRuntime ? LocalContextScope.SINGLETON : LocalContextScope.SINGLETHREAD);
ScriptingContainer jruby = new ScriptingContainer(scope);
// Search embulk/java/bootstrap.rb from a $LOAD_PATH.
// $LOAD_PATH is set by lib/embulk/command/embulk_run.rb if Embulk starts
// using embulk-cli but it's not set if Embulk is embedded in an application.
// Here adds this jar's internal resources to $LOAD_PATH for those applciations.
// List<String> loadPaths = new ArrayList<String>(jruby.getLoadPaths());
// String coreJarPath = JRubyScriptingModule.class.getProtectionDomain().getCodeSource().getLocation().getPath();
// if (!loadPaths.contains(coreJarPath)) {
// loadPaths.add(coreJarPath);
// }
// jruby.setLoadPaths(loadPaths);
if (gemHome != null) {
// Overwrites GEM_HOME and GEM_PATH. GEM_PATH becomes same with GEM_HOME. Therefore
// with this code, there're no ways to set extra GEM_PATHs in addition to GEM_HOME.
// Here doesn't modify ENV['GEM_HOME'] so that a JVM process can create multiple
// JRubyScriptingModule instances. However, because Gem loads ENV['GEM_HOME'] when
// Gem.clear_paths is called, applications may use unexpected GEM_HOME if clear_path
// is used.
jruby.callMethod(
jruby.runScriptlet("Gem"),
"use_paths", gemHome, gemHome);
}
// load embulk.rb
jruby.runScriptlet("require 'embulk'");
// jruby searches embulk/java/bootstrap.rb from the beginning of $LOAD_PATH.
jruby.runScriptlet("require 'embulk/java/bootstrap'");
// TODO validate Embulk::Java::Injected::Injector doesn't exist? If it already exists,
// Injector is created more than once in this JVM although use_global_ruby_runtime
// is set to true.
// set some constants
jruby.callMethod(
jruby.runScriptlet("Embulk::Java::Injected"),
"const_set", "Injector", injector);
jruby.callMethod(
jruby.runScriptlet("Embulk::Java::Injected"),
"const_set", "ModelManager", injector.getInstance(ModelManager.class));
jruby.callMethod(
jruby.runScriptlet("Embulk::Java::Injected"),
"const_set", "BufferAllocator", injector.getInstance(BufferAllocator.class));
// initialize logger
jruby.callMethod(
jruby.runScriptlet("Embulk"),
"logger=",
jruby.callMethod(
jruby.runScriptlet("Embulk::Logger"),
"new", injector.getInstance(ILoggerFactory.class).getLogger("ruby")));
return jruby;
}
public Set<Dependency<?>> getDependencies()
{
// get() depends on other modules
return ImmutableSet.of(
Dependency.get(Key.get(ModelManager.class)),
Dependency.get(Key.get(BufferAllocator.class)));
}
}
}