Overview
JavaScript is the default scripting language for Fess starting with 15.9. It runs on Sai (a Nashorn fork by CodeLibs that Fess already uses for its DI XML expressions), and scripts are executed as ECMAScript 6. Its identifier is javascript, and it can also be specified using the aliases js and sai.
How Scripts Are Evaluated
Fess’s scripting engine first tries to compile the script text as a single “expression.” Only if that fails to parse does it recompile the text as a block of “statements.”
Because of this, a simple expression that just returns a value:
and a script that contains a top-level return statement:
both work without issue. The latter is normally a syntax error in plain JavaScript, because a top-level return is not allowed. But since it cannot be compiled as an expression, it is reinterpreted as a statement block and runs as a valid script.
In places where each line is treated as a single expression, such as data store scripts, a script consisting of multiple statements cannot be used. In places where the entire script is evaluated, such as scheduled jobs, you can freely use multi-line statements, let / const variable declarations, and control-flow constructs.
Warning
A script that is compiled as a statement block returns a value only when it contains an explicit return. When the text fails to parse as an expression it is wrapped in a function and run as a block of statements, and a block with no return evaluates to null. A single trailing semicolon is enough to cross that line:
| Script | Result | Reason |
|---|---|---|
content.length() | 11 | Parses as an expression; the value of the expression is the result |
content.length(); | null | Parses only as a statement block, which contains no return |
var x = 1; x + 2 | null | Parses only as a statement block, which contains no return |
Under Groovy all three returned a value, because the value of the last statement evaluated is the script’s return value. JavaScript has no such rule.
This is the one difference in the migration that produces no error, no log line and no symptom other than a field quietly going empty: a data store mapping whose script returns null simply does not set that field. Write each data store field=expression line as a bare expression with no trailing semicolon, and give every scheduled job script an explicit return.
Basic Syntax
A line with no trailing semicolon below is an expression and can be used anywhere, including a data store field=expression line. Declarations ( let / const ), if blocks and loops are statements: they can only be used where the whole script is evaluated, such as a scheduled job, and the script must contain an explicit return to produce a value. See “How Scripts Are Evaluated” above.
Variable Declaration
String Operations
Collection Operations
Conditional Branching
Loop Processing
Data Store Scripts
Examples of scripts for data store configuration.
Note
In data store scripts, each field=expression line is evaluated independently as a single expression. Therefore, let / const variable-declaration statements and multi-line control structures that set several fields at once (such as if blocks) cannot be used. When using Java classes, write them as a single expression with a fully qualified class name (FQCN), and use a per-field ternary operator for conditional values (for example, url=data.published ? data.url : null ). Also, the variable name data used here is only an example; the actual variable name depends on the data store connector you use. See Data Store Crawling for details. Write the expression without a trailing semicolon: a line that can only be parsed as a statement block evaluates to null and the field is left unset — see How Scripts Are Evaluated.
Basic Mapping
URL Generation
Content Processing
Date Processing
Available Objects
The objects available in scripts vary depending on the execution context.
| Context | Object | Description |
|---|---|---|
| All contexts | container | DI container. Used to access components via container.getComponent("...") |
| Scheduled jobs | executor | Job execution control ( JobExecutor ). Required for job stop support |
| Data store | (connector-specific) | Data record variables provided by each data store. The variable name depends on the connector |
| Path mapping | url , matcher | The URL string to convert and the regular-expression match result ( Matcher ). Available when the replacement is prefixed with a registered engine name, such as javascript: (aliases js:, sai:) |
| Document boost | (document fields) | Each field of the target document is available as a variable (used in condition and boost-value expressions) |
Scheduled Job Scripts
Examples of JavaScript scripts used in scheduled jobs. In scheduled jobs, container and executor are available. Passing executor to the job’s execute() method enables job stop control.
Note
A scheduled job script is evaluated as a single, complete script. The scripting engine first tries to compile it as an expression and reinterprets it as a block of statements only if that fails, so multi-line statements, let / const declarations, control-flow constructs, and a top-level return statement can all be used (see “How Scripts Are Evaluated” above for details). The “Using Java Classes”, “Accessing Fess Components”, “Error Handling”, and “Debugging and Log Output” examples below also assume this complete-script context.
Execute Crawl Job
Conditional Crawling
Execute Multiple Jobs Sequentially
Using Java Classes
Within JavaScript scripts, Sai’s (Nashorn’s) Java interoperability lets you use Java standard libraries and Fess classes directly. JavaScript has no import statement, so classes are always written by their fully qualified name (FQCN).
Date and Time
File Operations
HTTP Communication
Warning
Access to external resources affects performance, so keep it to a minimum.
Accessing Fess Components
You can access Fess components using container.
System Helper
Getting Configuration Values
Executing Searches
Error Handling
JavaScript has no import statement, so there is no Groovy-style placement restriction to worry about. You can catch exceptions with try-catch to control job errors.
Debugging and Log Output
Log Output
Debug Output
If you want to quickly inspect the contents of a variable, stringify it with JSON.stringify and log it.
Porting from Groovy
Keep the following differences in mind when porting an existing Groovy script to JavaScript.
Arithmetic Precision
JavaScript number arithmetic is always double-precision floating point. For example, the following expression returns the integer 34 in Groovy, but a floating-point 34.0 in JavaScript.
On the other hand, the return type of a method called through Java interop keeps its Java-side type, so content.length() still returns an integer.
Rewriting Groovy-Only Syntax
The following Groovy-only syntax must be rewritten for JavaScript.
| Groovy | JavaScript | Description |
|---|---|---|
1000L | 1000 | The L long-literal suffix is not needed; write the number literal as-is |
["a", "b"] as String[] | ["a", "b"] | A JavaScript array is automatically converted to a Java array when passed to a method that takes |
Java Interoperability
Java interoperability uses the same notation as Nashorn, and is nearly identical to Groovy’s. Fully qualified constructor calls such as new java.io.File(...), java.lang.System.getProperty(...), and new org.codelibs.fess.job.IndexExportJob() all resolve as-is.
ES6 Syntax
Because Fess’s JavaScript engine runs as ECMAScript 6, you can use ES6 syntax such as let / const, arrow functions, template literals, destructuring, for...of, and class. However, optional chaining (?.) and the nullish coalescing operator (??) are ES2020-and-later syntax and cannot be used.
Best Practices
Keep it simple: Avoid complex logic and write readable code
Default values: Use the logical OR operator (
||) in place of the Elvis operatorException handling: Handle unexpected errors with appropriate try-catch
Log output: Output logs for easier debugging
Performance: Minimize external resource access
Numeric arithmetic: Where an integer is expected, either use the result of a Java interop method call directly, or convert explicitly where needed
Reference Information
Scripting Overview - Scripting Overview
Groovy Scripting Guide - Groovy Scripting Guide (plugin)
Data Store Crawling - Data Store Configuration Guide
Scheduler - Scheduler Configuration Guide