It is a number of years now since I decided to commit to Java, for a number of reasons. The biggest were:
- lack of good compile-time validation, which meant it was possible to mistype a variable name or method name
- lack of templating, which meant constantly typing try/catch blocks
- difficulty troubleshooting – this was before the SSJS debugger, but even when the debugger was released, I struggled to work well with it
A little while after starting with Java I had a discussion with Tim Tripcony. Typically for Tim he explained exactly what SSJS was, which makes all those limitations totally understandable and reinforced my conviction that less SSJS and more Java was a no-brainer.
The Bad
First of all, SSJS is not JavaScript. Many questions I’ve seen on StackOverflow seem to suggest some developers confuse it with JavaScript, thinking it runs on the browser. It is also probably why many developers seem to prefer creating Arrays and JavaScript objects in SSJS rather than getting used to using Java Lists or Sets or Maps. This means developers struggle with things like ordering or ensuring unique entries only, things which can be automatically handled by using the right List / Set / Map.
Secondly, and most importantly, it’s not really a language. There is no SSJS engine, as such, within the XPages runtime. This becomes particularly apparent when you look at what actually runs on the server. This means going to the Package Explorer view and looking at the Local\xsp Java package, which holds the Java classes compiled from the XML markup for each XPage and Custom Control – which further reinforces that XPages is Java. Each component (Computed Text, Button etc) is a separate Java object created by its own method when the page is initially created. Here is the code for a button with a SSJS rendered property. Note, the corresponding eventHandler is also a component, separate from the button component, with its own properties.
-
private UIComponent createButton1(FacesContext context,
-
UIComponent parent, PageExpressionEvaluator evaluator) {
-
XspCommandButton result = new XspCommandButton();
-
result.setValue(“Submit”);
-
String renderedExpr = “#{javascript:if (view.isRenderingPhase()) {\n\tif (document1.getItemValueString(\”Name\”)==\”\”) {\n\t\treturn false;\n\t}\n\tif (facesContext.getMessages().hasNext()) {\n\t\treturn false;\n\t}\n\treturn true;\n}}”;
-
ValueBinding rendered = evaluator.createValueBinding(result, renderedExpr, sourceId,boolean.class);
-
result.setValueBinding(“rendered”, rendered);
-
setId(result, “button1”);
-
return result;
-
}
The particularly relevant lines are lines 6 – 8. The Server-Side JavaScript is stored in a Java String, which is passed into a ValueBinding (for properties on a component, a ValueBinding is created, for actions in an eventHandler MethodBindings are created). Since that discussion with Tim Tripcony, XPages was further enhanced to allow caching of SSJS. Basically, this is done by hashing the resulting String allowing each to be cross-referenced.
But it’s still a String, and what happens at runtime is what Tim explained to me. The String is parsed by the XPages runtime using a specific parser. The key part is “javascript” keyword, which defines which parser to use (some time ago Jesse Gallagher added a parser for Ruby as well). The parser – whichever parser – then needs to look for separators. Tim explained this was the reason that, although semi-colons aren’t mandatory for separating chunks of code, they are best practice because the parser identifies them as separators. Obviously braces are another, possibly line separators are also used, I don’t know. One point that occurs to me as I review this is the tab (“\t”). This improves readability, but will need to be handled by the parser.
Once a chunk of code has been separated, it needs parsing. There will be some specific keywords, like “if”, “else”, “var”, “:” (used to cast a variable to a Java class). Those keywords can easily be handled, so the first four characters – if (
can be quickly handled.
If a word is not a keyword, then it needs processing. Most developers are probably unaware of what happens next, but it was something I identified a while back by adding a print statement to a custom VariableResolver. Once a word that’s not a keyword is identified, it’s passed to the VariableResolver. The VariableResolver is queried to find the variable name (in the first case “view”) in one of the scopes, working from requestScope out to applicationScope (or further, if available). In the case of a line of SSJS that initialises a variable, presumably it is added to requestScope once initialised, to be removed once the SSJS has been processed or at the latest once the component has been processed.
In the case of parameters, like in document.getItemValueString("Name")
it gets more complex. That’s because those parameters need converting, from SSJS “variable types” to Java classes. This is probably less well known, unless you’ve dug into the OpenNTF Domino API or Extension Library. The comment in the Extension Library example explains things very well:
// Each function expose by a library can then have one or multiple
// “prototypes”, defining its parameters and the returned value type. To
// make this definition as efficient as possible, the parameter definition
// is compacted within a string, where all the parameters are defined
// within parenthesis followed by the returned value type.
// A parameter is defined by its name, followed by a colon and its type.
// Generally, the type is defined by a single character (see bellow) or a
// full Java class name. The returned type is defined right after the
// closing parameter parenthesis.
//
// Here is, for example, the definition of the “@Date” function which can
// take 3 different set of parameters:
// “(time:Y):Y”,
// “(years:Imonths:Idays:I):Y”,
// “(years:Imonths:Idays:Ihours:Iminutes:Iseconds:I):Y”);
So even though getItemValueString()
takes a String and Java also has a String, there still needs to be some comparison, both converting the parameter and converting the result. This is why it can cause problems when calling Java methods from SSJS with different variable types, like numeric ones.
This is why my preference is even more to use Java, even if it means using a single line of SSJS to call Java.
The Good
SSJS is not completely bad. It’s easy to screw things up when coding it. How many developers have forgotten to add “var”, which means the variable is not instantiated and attempts to use it fail? And how many have written SSJS that includes a typo, especially when not casting variables to an SSJS / Java class? It’s easily done.
But the SSJS editor is reasonably good in the typeahead support, although it only works for core classes. When an XPages app dev roadmap existed, enhancements to typeahead support were planned to extend it to include custom Java classes. Who knows if that will ever be delivered. But the editor helps a lot, which is also why I would recommend casting variables to SSJS / Java classes.
But the key point is that, even though it’s compiled down to an escaped String in the underlying Java class behind each XPage or Custom Control, the development experience is via the SSJS editor. Thankfully we don’t need to write SSJS into a Java String variable directly (reminiscent of writing print statements to pass to a browser from a LotusScript web agent).
The Ugly
As I’ve started looking at other database types, the learning from SSJS covered in this blog post and particularly that last point have been at the forefront of my mind.
One of the first databases I looked at was Cloudant, because it is NoSQL, IBM, Bluemix and so offered options as an alternative to Domino while still using XPages. Then I read how Cloudant views are defined and stored – by writing a JavaScript function as a String property stored in a View Design document. This is very reminiscent of SSJS and looks horrendous for coding, supporting, troubleshooting, validating etc.
I’ve done a lot on Graph recently and Neo4J is a popular Graph database. Similarly any interaction with the database seems to be through statements written as and stored in strings.
Apache Cassandra has a document API query language, CQL. That allows the same kind of thing, executing CQL statements written as Strings from Java, as covered in the first part of the introduction tutorial, which are executed against the session as detailed in the Java Driver API documentation. But the Java API also provides Java methods for each part of those statements, which the second part introduces quite promptly, which are loaded into a QueryBuilder as detailed in the API documentation. This makes me feel much more comfortable because it is less error prone as well as showing a provider committed to Java.
Compare also the Java API examples for IBM Graph (which uses Titan, a Java API based on Tinkerpop). This requires you to create JSON queries to the Gremlin server and convert the JSON data (which has already been converted from Java to JSON by the Gremlin server) back into Java. Needless to say, that’s also very error prone and frustrating for anyone familiar with Tinkerpop or graph databases.
So as bad as SSJS may seem, the editor is worth applauding and without it, many Domino developers would not have embraced XPages.
Wow, excellent explanation.
I absolutley agree with “So as bad as SSJS may seem, the editor is worth applauding and without it, many Domino developers would not have embraced XPages.”
SSJS was a bridge to using Java directly with XPages and once the bridge was crossed ( with helpful cajoling from the likes of Matt White ) there was no looking back 🙂
Without SSJS XPages would never have taken off.
I did feel a bit for IBM as I ( and others ) had made a big fuss about there being no debugger for SSJS and then by the time it was available we had moved on to Java.