-
-
Notifications
You must be signed in to change notification settings - Fork 458
Description
There are some challenges with the current implementation, and the need for a redesign has been discussed in #4797.
The situation is the following: Scripting engines have to implement ScriptEngineFactory. This interface only contains one method to identify the scripting engine:
/**
* This method returns a list of file extensions and MimeTypes that are supported by the ScriptEngine, e.g. py,
* application/python, js, application/javascript, etc.
*
* @return List of supported script types
*/
List<String> getScriptTypes();These are plain strings and can contain anything, and each scripting engine usually provides multiple "script type" strings. Anything that wants to utilize a scripting engine must use one of these "script types" for the task to be picked up by the correct scripting engine. These "script types" are supposed to be a combination of supported file extensions and MIME types (although scripting engines don't really care about MIME types). For all this to work, the "script types" to use must be predictable and unique for one scripting engine.
Java, though its JSR223 support, provides interface ScriptEngineFactory, which defines:
public interface ScriptEngineFactory {
/**
* Returns the full name of the <code>ScriptEngine</code>. For
* instance an implementation based on the Mozilla Rhino Javascript engine
* might return <i>Rhino Mozilla Javascript Engine</i>.
* @return The name of the engine implementation.
*/
public String getEngineName();
/**
* Returns the version of the <code>ScriptEngine</code>.
* @return The <code>ScriptEngine</code> implementation version.
*/
public String getEngineVersion();
/**
* Returns an immutable list of filename extensions, which generally identify scripts
* written in the language supported by this <code>ScriptEngine</code>.
* The array is used by the <code>ScriptEngineManager</code> to implement its
* <code>getEngineByExtension</code> method.
* @return The list of extensions.
*/
public List<String> getExtensions();
/**
* Returns an immutable list of mimetypes, associated with scripts that
* can be executed by the engine. The list is used by the
* <code>ScriptEngineManager</code> class to implement its
* <code>getEngineByMimetype</code> method.
* @return The list of mime types.
*/
public List<String> getMimeTypes();
/**
* Returns an immutable list of short names for the <code>ScriptEngine</code>, which may be used to
* identify the <code>ScriptEngine</code> by the <code>ScriptEngineManager</code>.
* For instance, an implementation based on the Mozilla Rhino Javascript engine might
* return list containing {"javascript", "rhino"}.
* @return an immutable list of short names
*/
public List<String> getNames();
/**
* Returns the name of the scripting language supported by this
* <code>ScriptEngine</code>.
* @return The name of the supported language.
*/
public String getLanguageName();
/**
* Returns the version of the scripting language supported by this
* <code>ScriptEngine</code>.
* @return The version of the supported language.
*/
public String getLanguageVersion();The practice in most OH scripting engine implementations is to simply query the ScriptEngineFactory for both getExtensions() and getMimeTypes() and lump these into one list of string and register these as "script types".
These are outside the control of OH and can be anything. It is difficult to make sure that these are both predictable and unique. Since the official scripting engine add-ons are few, this is currently handled by modifying the scripting engine to report some, strictly invalid, values for "script types" when a newer version comes along. For Nashorn JavaScript, file extensions has been manually specified to only nashornjs (which isn't really a thing), and ;version=ECMAScript-5.1 is appended to the MIME types retrieved from the ScriptEngineFactory (to prevent collision with those provided by Graal JavaScript). For Jython, the "list" as been hardcoded to consist of jythonpy and application/x-python2, which are neither valid file extensions nor MIME types.
When these "script types" are to be used, there are other "creative" solutions. For transformations, the shortest (in number of characters) string (if there are two of the same length, it's unknown which one is used) is made upper-case and then used as the "code" you have to apply to use a certain scripting engine for transformations.
For managed rules through MainUI, a completely different solution is used. Here, the module types are queries from the REST API, and the list of options from the type of the ConfigDescriptionParameter that corresponds to "a script" is parsed into a name and a version. Below is an example of such a REST API response:
{
"inputs": [],
"outputs": [
{
"name": "result",
"type": "java.lang.Object",
"label": "result",
"description": "the script result"
}
],
"uid": "script.ScriptAction",
"visibility": "VISIBLE",
"tags": [],
"label": "Execute an inline script",
"description": "Executes a user-defined script",
"configDescriptions": [
{
"description": "The scripting language used",
"label": "Script Type",
"name": "type",
"required": true,
"type": "TEXT",
"readOnly": false,
"multiple": false,
"advanced": false,
"verify": false,
"limitToOptions": true,
"options": [
{
"label": "Rule DSL (v1)",
"value": "application/vnd.openhab.dsl.rule"
},
{
"label": "Groovy (4.0.26)",
"value": "application/x-groovy"
},
{
"label": "Python (2.7)",
"value": "application/x-python2"
},
{
"label": "Ruby (jruby 10.0.0.1)",
"value": "application/x-ruby"
}
],
"filterCriteria": []
},
{
"context": "script",
"description": "The script to execute",
"label": "Script",
"name": "script",
"required": true,
"type": "TEXT",
"readOnly": false,
"multiple": false,
"advanced": false,
"verify": false,
"limitToOptions": true,
"options": [],
"filterCriteria": []
}
]
},These values are originally built by querying the ScriptEngineFactory implementations for getLanguageName() and getLanguageVersion() which are then concatenated into a string with the "version" in parentheses.
As a result, there is no consistency for users of how to refer to a scripting engine across transformations and rules/scripts, even for official add-ons. Add to that, the "random nature" of what ends up as these codes for unofficial add-ons. Also, with "the new YAML format" (#3666), support for rules have already been implemented in #4633 and support for transformations is planned in the future. Trying to figure out how to refer to scripting engines from these files has been a headache. For now, we have landed on using aliases that translate "the commonly most used codes" into MIME types that are understood by the rest of the system, but these aliases only exist for official add-ons and require their own "maintenance" as things change in the future. It's thus far from ideal.
It would be beneficial if there was one way to refer to scripting engines that was used everywhere, and even better if the system prevented registration of "colliding" codes.
Personally, I don't think MIME types should be used here at all, since they are such a mess in themselves (with the official IANA registry versus all the "ad hock" codes in wide use, and the fact that they are meant to identify file/document types, not scripting languages). The same could be said about file extensions, they aren't strictly very relevant, and there will be overlap between scripting engines. All that's needed is one (or more) unique code(s) representing a particular scripting engine. MIME types are used for example in MainUI for syntax highlighting and code completion, but since MIME types are such a mess, and OH's "challenge" with keeping them unique, there already exists code in MainUI that "translates" MIME types into something that works for a specific purpose. Since they can't be used as they are anyway, I'd say that they serve no real purpose. It would be easier if MainUI knew the scripting language and potentially version (Python 2 and Python 3 have different syntax highlighting for example), and then picked the correct MIME type to use from that.
Regardless of the MIME types question, it should be obvious that it would be helpful to find a more organized way to handle all this.
I'm creating this issue in the hope that some better solution could be agreed upon.