| org.openhab.automation.jrubyscripting:rubylib | $OPENHAB_CONF/automation/lib/ruby/ | Search path for user libraries. Separate each path with a colon (semicolon in Windows). |
| org.openhab.automation.jrubyscripting:local_context | singlethread | The local context holds Ruby runtime, name-value pairs for sharing variables between Java and Ruby. See [this](https://github.com/jruby/jruby/wiki/RedBridge#Context_Instance_Type) for options and details |
| org.openhab.automation.jrubyscripting:local_variables | transient | Defines how variables are shared between Ruby and Java. See [this](https://github.com/jruby/jruby/wiki/RedBridge#local-variable-behavior-options) for options and details |
-| org.openhab.automation.jrubyscripting:gems | | Comma separated list of [Ruby Gems](https://rubygems.org/) to install. |
+| org.openhab.automation.jrubyscripting:gems | | A comma separated list of [Ruby Gems](https://rubygems.org/) to install. |
+| org.openhab.automation.jrubyscripting:require | | A comma separated list of script names to be required by the JRuby Scripting Engine at the beginning of user scripts. |
## Ruby Gems
Gem versions may be specified using the standard ruby gem_name=version format.
The version number follows the [pessimistic version constraint](https://guides.rubygems.org/patterns/#pessimistic-version-constraint) syntax.
-For example this configuration will install version 4 or higher of the [openHAB JRuby Scripting Library](https://boc-tothefuture.github.io/openhab-jruby/).
+For example this configuration will install the latest version of the [openHAB JRuby Scripting Library](https://boc-tothefuture.github.io/openhab-jruby/), and instruct the scripting engine to automatically insert `require 'openhab'` at the start of the script.
```text
-org.openhab.automation.jrubyscripting:gems=openhab-scripting=~>4.0
+org.openhab.automation.jrubyscripting:gems=openhab-scripting
+org.openhab.automation.jrubyscripting:require=openhab
```
## Creating JRuby Scripts
All [ScriptExtensions]({{base}}/configuration/jsr223.html#scriptextension-objects-all-jsr223-languages) are available in JRuby with the following exceptions/modifications:
-- The File variable, referencing java.io.File is not available as it conflicts with Ruby's File class preventing Ruby from initializing
-- Globals scriptExtension, automationManager, ruleRegistry, items, voice, rules, things, events, itemRegistry, ir, actions, se, audio, lifecycleTracker are prepended with a $ (e.g. $automationManager) making them available as a global objects in Ruby.
+- The `File` variable, referencing `java.io.File` is not available as it conflicts with Ruby's File class preventing Ruby from initializing
+- Globals `scriptExtension`, `automationManager`, `ruleRegistry`, `items`, `voice`, `rules`, `things`, `events`, `itemRegistry`, `ir`, `actions`, `se`, `audio`, `lifecycleTracker` are prepended with a `$` (e.g. `$automationManager`) making them available as global objects in Ruby.
## Script Examples
require 'java'
java_import org.slf4j.LoggerFactory
-LoggerFactory.getLogger("org.openhab.core.automation.examples").info("Hello world!")
+LoggerFactory.getLogger("org.openhab.automation.examples").info("Hello world!")
```
JRuby can [import Java classes](https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby).
-Depending on the openHAB logging configuration, you may need to prefix logger names with `org.openhab.core.automation` for them to show up in the log file (or you modify the logging configuration).
+Depending on the openHAB logging configuration, you may need to prefix logger names with `org.openhab.automation` for them to show up in the log file (or you modify the logging configuration).
+
+**Note**: Installing the [JRuby Scripting Library](https://boc-tothefuture.github.io/openhab-jruby/installation/) will provide enhanced capabilities with simpler rule syntax.
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
* Processes JRuby Configuration Parameters.
*
* @author Brian O'Connell - Initial contribution
+ * @author Jimmy Tanagra - Add $LOAD_PATH, require injection
*/
@NonNullByDefault
public class JRubyScriptEngineConfiguration {
private static final String GEM_HOME = "gem_home";
private static final String RUBYLIB = "rubylib";
+ private static final String GEMS = "gems";
+ private static final String REQUIRE = "require";
// Map of configuration parameters
private static final Map<String, OptionalConfigurationElement> CONFIGURATION_PARAMETERS = Map.ofEntries(
new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT)
.mappedTo("RUBYLIB").defaultValue(DEFAULT_RUBYLIB.toString()).build()),
- Map.entry("gems", new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.GEM).build()));
+ Map.entry(GEMS, new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.GEM).build()),
+
+ Map.entry(REQUIRE,
+ new OptionalConfigurationElement.Builder(OptionalConfigurationElement.Type.REQUIRE).build()));
private static final Map<OptionalConfigurationElement.Type, List<OptionalConfigurationElement>> CONFIGURATION_TYPE_MAP = CONFIGURATION_PARAMETERS
.values().stream().collect(Collectors.groupingBy(v -> v.type));
* @param factory Script Engine to configure
*/
void configureScriptEngine(ScriptEngineFactory factory) {
- configureSystemProperties(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.SYSTEM_PROPERTY,
- Collections.<OptionalConfigurationElement> emptyList()));
+ configureSystemProperties();
ScriptEngine engine = factory.getScriptEngine();
-
- configureRubyEnvironment(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT,
- Collections.<OptionalConfigurationElement> emptyList()), engine);
-
- configureGems(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.GEM,
- Collections.<OptionalConfigurationElement> emptyList()), engine);
+ configureRubyEnvironment(engine);
+ configureGems(engine);
}
/**
*/
private void ensureGemHomeExists() {
OptionalConfigurationElement gemHomeConfigElement = CONFIGURATION_PARAMETERS.get(GEM_HOME);
- if (gemHomeConfigElement != null) {
- Optional<String> gemHome = gemHomeConfigElement.getValue();
- if (gemHome.isPresent()) {
- File gemHomeDirectory = new File(gemHome.get());
- if (!gemHomeDirectory.exists()) {
- logger.debug("gem_home directory does not exist, creating");
- if (!gemHomeDirectory.mkdirs()) {
- logger.debug("Error creating gem_home direcotry");
- }
+ if (gemHomeConfigElement == null) {
+ return;
+ }
+ Optional<String> gemHome = gemHomeConfigElement.getValue();
+ if (gemHome.isPresent()) {
+ File gemHomeDirectory = new File(gemHome.get());
+ if (!gemHomeDirectory.exists()) {
+ logger.debug("gem_home directory does not exist, creating");
+ if (!gemHomeDirectory.mkdirs()) {
+ logger.warn("Error creating gem_home directory");
}
- } else {
- logger.debug("Gem install requested without gem_home specified, not ensuring gem_home path exists");
}
+ } else {
+ logger.debug("Gem install requested without gem_home specified, not ensuring gem_home path exists");
}
}
/**
* Install a gems in ScriptEngine
*
- * @param gemsDirectives List of gems to install
* @param engine Engine to install gems
*/
- private synchronized void configureGems(List<OptionalConfigurationElement> gemDirectives, ScriptEngine engine) {
- for (OptionalConfigurationElement gemDirective : gemDirectives) {
- if (gemDirective.getValue().isPresent()) {
- ensureGemHomeExists();
-
- String[] gems = gemDirective.getValue().get().split(",");
- for (String gem : gems) {
- gem = gem.trim();
- String gemCommand;
- if (gem.contains("=")) {
- String[] gemParts = gem.split("=");
- gem = gemParts[0];
- String version = gemParts[1];
- gemCommand = "Gem.install('" + gem + "',version='" + version + "')\n";
- } else {
- gemCommand = "Gem.install('" + gem + "')\n";
- }
-
- try {
- logger.debug("Installing Gem: {} ", gem);
- logger.trace("Gem install code:\n{}\n", gemCommand);
- engine.eval(gemCommand);
- } catch (Exception e) {
- logger.error("Error installing Gem", e);
- }
- }
+ private synchronized void configureGems(ScriptEngine engine) {
+ ensureGemHomeExists();
+
+ OptionalConfigurationElement gemsConfigElement = CONFIGURATION_PARAMETERS.get(GEMS);
+ if (gemsConfigElement == null || !gemsConfigElement.getValue().isPresent()) {
+ return;
+ }
+
+ String[] gems = gemsConfigElement.getValue().get().split(",");
+ for (String gem : gems) {
+ gem = gem.trim();
+ String version = "";
+ String gemCommand;
+ if (gem.contains("=")) {
+ String[] gemParts = gem.split("=");
+ gem = gemParts[0].trim();
+ version = gemParts[1].trim();
+ }
+
+ if (gem.isEmpty()) {
+ continue;
+ } else if (version.isEmpty()) {
+ gemCommand = "Gem.install('" + gem + "')\n";
} else {
- logger.debug("Ruby gem property has no value");
+ gemCommand = "Gem.install('" + gem + "', '" + version + "')\n";
+ }
+
+ try {
+ logger.debug("Installing Gem: {}", gem);
+ logger.trace("Gem install code:\n{}\n", gemCommand);
+ engine.eval(gemCommand);
+ } catch (ScriptException e) {
+ logger.warn("Error installing Gem: {}", e.getMessage());
+ } catch (BootstrapMethodError e) {
+ logger.warn("Error while checking/installing gems: {}. You may need to restart OpenHAB",
+ e.getMessage());
+ logger.debug("Error in configureGems", e);
}
}
}
/**
- * Configure the base Ruby Environment
+ * Execute ruby require statement in the ScriptEngine
*
- * @param engine Engine to configure
+ * @param engine Engine to insert the require statements
*/
- public ScriptEngine configureRubyEnvironment(ScriptEngine engine) {
- configureRubyEnvironment(CONFIGURATION_TYPE_MAP.getOrDefault(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT,
- Collections.<OptionalConfigurationElement> emptyList()), engine);
- return engine;
+ public void injectRequire(ScriptEngine engine) {
+ OptionalConfigurationElement requireConfigElement = CONFIGURATION_PARAMETERS.get(REQUIRE);
+ if (requireConfigElement == null || !requireConfigElement.getValue().isPresent()) {
+ return;
+ }
+
+ String[] scripts = requireConfigElement.getValue().get().split(",");
+ for (String script : scripts) {
+ final String requireStatement = String.format("require '%s'", script.trim());
+ try {
+ logger.trace("Injecting require statement: {}", requireStatement);
+ engine.eval(requireStatement);
+ } catch (ScriptException e) {
+ logger.warn("Error evaluating statement {}: {}", requireStatement, e.getMessage());
+ }
+ }
}
/**
* Configure the optional elements of the Ruby Environment
*
- * @param optionalConfigurationElements Optional elements to configure in the ruby environment
* @param engine Engine in which to configure environment
*/
- private void configureRubyEnvironment(List<OptionalConfigurationElement> optionalConfigurationElements,
- ScriptEngine engine) {
- for (OptionalConfigurationElement configElement : optionalConfigurationElements) {
- String environmentProperty = configElement.mappedTo().get();
- if (configElement.getValue().isPresent()) {
- String environmentSetting = "ENV['" + environmentProperty + "']='" + configElement.getValue().get()
- + "'";
- try {
- logger.trace("Setting Ruby environment with code: {} ", environmentSetting);
- engine.eval(environmentSetting);
- } catch (ScriptException e) {
- logger.error("Error setting ruby environment", e);
- }
- } else {
- logger.debug("Ruby environment property ({}) has no value", environmentProperty);
+ public ScriptEngine configureRubyEnvironment(ScriptEngine engine) {
+ getConfigurationElements(OptionalConfigurationElement.Type.RUBY_ENVIRONMENT).forEach(configElement -> {
+ final String environmentSetting = String.format("ENV['%s']='%s'", configElement.mappedTo().get(),
+ configElement.getValue().get());
+ try {
+ logger.trace("Setting Ruby environment with code: {} ", environmentSetting);
+ engine.eval(environmentSetting);
+ } catch (ScriptException e) {
+ logger.warn("Error setting ruby environment", e);
}
- }
+ });
configureRubyLib(engine);
+ return engine;
}
/**
*
* @param optionalConfigurationElements Optional system properties to configure
*/
- private void configureSystemProperties(List<OptionalConfigurationElement> optionalConfigurationElements) {
- for (OptionalConfigurationElement configElement : optionalConfigurationElements) {
+ private void configureSystemProperties() {
+ getConfigurationElements(OptionalConfigurationElement.Type.SYSTEM_PROPERTY).forEach(configElement -> {
String systemProperty = configElement.mappedTo().get();
- if (configElement.getValue().isPresent()) {
- String propertyValue = configElement.getValue().get();
- logger.trace("Setting system property ({}) to ({})", systemProperty, propertyValue);
- System.setProperty(systemProperty, propertyValue);
- } else {
- logger.warn("System property ({}) has no value", systemProperty);
- }
- }
+ String propertyValue = configElement.getValue().get();
+ logger.trace("Setting system property ({}) to ({})", systemProperty, propertyValue);
+ System.setProperty(systemProperty, propertyValue);
+ });
+ }
+
+ private Stream<OptionalConfigurationElement> getConfigurationElements(
+ OptionalConfigurationElement.Type configurationType) {
+ return CONFIGURATION_TYPE_MAP
+ .getOrDefault(configurationType, Collections.<OptionalConfigurationElement> emptyList()).stream()
+ .filter(element -> element.getValue().isPresent());
}
/**
private enum Type {
SYSTEM_PROPERTY,
RUBY_ENVIRONMENT,
- GEM
+ GEM,
+ REQUIRE
}
private static class Builder {
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="automation:jruby">
- <parameter-group name="system">
- <label>System Properties</label>
- <description>This group defines JRuby system properties.</description>
- <advanced>true</advanced>
+ <parameter-group name="gems">
+ <label>Ruby Gems</label>
+ <description>This group defines the list of Ruby Gems to install.</description>
</parameter-group>
<parameter-group name="environment">
<label>Ruby Environment</label>
<description>This group defines Ruby's environment.</description>
- <advanced>false</advanced>
</parameter-group>
- <parameter-group name="gems">
- <label>Ruby Gems</label>
- <description>This group defines the list of Ruby Gems to install.</description>
- <advanced>false</advanced>
+ <parameter-group name="system">
+ <label>System Properties</label>
+ <description>This group defines JRuby system properties.</description>
</parameter-group>
+ <parameter name="gems" type="text" required="false" groupName="gems">
+ <label>Ruby Gems</label>
+ <description>A comma separated list of Ruby Gems to install.</description>
+ </parameter>
+
+ <parameter name="require" type="text" required="false" groupName="gems">
+ <label>Require Scripts</label>
+ <description>A comma separated list of script names to be required by the JRuby Scripting Engine before running user
+ scripts.</description>
+ </parameter>
+
+ <parameter name="gem_home" type="text" required="false" groupName="environment">
+ <label>GEM_HOME</label>
+ <description>Location Ruby Gems will be installed and loaded, directory will be created if missing and gem installs
+ are specified. Defaults to "OPENHAB_CONF/scripts/lib/ruby/gem_home" when not specified.</description>
+ </parameter>
+
+ <parameter name="rubylib" type="text" required="false" groupName="environment">
+ <label>RUBYLIB</label>
+ <description>Search path for user libraries. Separate each path with a colon (semicolon in Windows). Defaults to
+ "OPENHAB_CONF/automation/lib/ruby" when not specified.</description>
+ </parameter>
+
<parameter name="local_context" type="text" required="false" groupName="system">
<label>Context Instance Type</label>
<description>The local context holds Ruby runtime, name-value pairs for sharing variables between Java and Ruby. See
<option value="singlethread">SingleThread</option>
<option value="concurrent">Concurrent</option>
</options>
+ <advanced>true</advanced>
</parameter>
<parameter name="local_variable" type="text" required="false" groupName="system">
<option value="persistent">Persistent</option>
<option value="global">Global</option>
</options>
+ <advanced>true</advanced>
</parameter>
- <parameter name="gem_home" type="text" required="false" groupName="environment">
- <label>GEM_HOME</label>
- <description>Location Ruby Gems will be installed and loaded, directory will be created if missing and gem installs
- are specified</description>
- <default></default>
- </parameter>
-
- <parameter name="rubylib" type="text" required="false" groupName="environment">
- <label>RUBYLIB</label>
- <description>Search path for user libraries. Separate each path with a colon (semicolon in Windows).</description>
- </parameter>
-
- <parameter name="gems" type="text" required="false" groupName="gems">
- <label>Ruby Gems</label>
- <description>Comma separated list of Ruby Gems to install.</description>
- </parameter>
</config-description>
</config-description:config-descriptions>