Skip to content

Commit 73cd227

Browse files
authored
Modernize example script engine and tests (#14)
1 parent 03cbd93 commit 73cd227

7 files changed

Lines changed: 196 additions & 455 deletions

File tree

.travis.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

README.md

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,41 @@
55

66
A simple example plugin demonstrating how to create custom script engines for [Fess](https://fess.codelibs.org/), the open-source enterprise search server.
77

8+
## What Is a Script Engine?
9+
10+
A Fess script engine turns a **template** plus a **parameter map** into a value. Fess uses script
11+
engines wherever an administrator can enter a small script or expression — for example to compute a
12+
document boost, derive a field value during crawling, or run logic in a scheduled job. The built-in
13+
`groovy` engine evaluates full Groovy scripts; this example shows the minimal shape of a custom one.
14+
815
## Overview
916

10-
This plugin provides a minimal implementation of a custom script engine for Fess. The `ExampleEngine` serves as a template and starting point for developers who want to create their own script engines with custom template processing logic.
17+
This plugin provides a minimal implementation of a custom script engine for Fess. The `ExampleEngine`
18+
serves as a template and starting point for developers who want to create their own script engines
19+
with custom template processing logic.
1120

1221
### Key Features
1322

14-
- **Simple Implementation**: Demonstrates the basic structure of a Fess script engine
15-
- **Template Pass-through**: Returns template strings unchanged (useful for testing and learning)
16-
- **Full Integration**: Properly integrated with Fess's dependency injection container
17-
- **Comprehensive Tests**: Includes extensive test cases covering edge cases and various scenarios
23+
- **Real, Minimal Evaluation**: Performs simple `${key}` placeholder substitution from the parameter map
24+
- **Idiomatic Structure**: Demonstrates the standard structure of a Fess script engine
25+
- **Self-Registration**: Registers itself with the script engine factory via the DI container
26+
- **Focused Tests**: Meaningful tests covering substitution, missing keys, null/blank input, and factory lookup
1827

1928
## Architecture
2029

2130
The plugin extends Fess's `AbstractScriptEngine` class and implements:
2231

23-
- **Template Evaluation**: Process template strings with parameter maps
24-
- **Engine Identification**: Provides a unique name ("example") for the script engine
25-
- **DI Integration**: Configured via LastaDi container for seamless Fess integration
32+
- **Template Evaluation** (`evaluate`): Substitutes `${key}` placeholders with values from the parameter map.
33+
A blank template returns `null`; a missing or `null` value leaves the placeholder untouched.
34+
- **Engine Identification** (`getName`): Provides the unique name (`"example"`) used to register and look up the engine
35+
- **DI Integration**: `fess_se++.xml` registers the engine into Fess's `scriptEngineFactory` at startup
36+
via a `register` postConstruct (the `++` suffix means the fragment is additively merged into the core `fess_se.xml`)
2637

2738
## Installation
2839

2940
### Prerequisites
3041

31-
- Fess 15.0.0 or later
42+
- Fess 15.7.0 or later
3243
- Java 21 or later
3344

3445
### Download
@@ -42,20 +53,28 @@ You can download the plugin JAR from [Maven Central](https://repo1.maven.org/mav
4253
3. Restart Fess server
4354
4. The "example" script engine will be available for use
4455

45-
For detailed installation instructions, see the [Fess Plugin Guide](https://fess.codelibs.org/15.0/admin/plugin-guide.html).
56+
For detailed installation instructions, see the [Fess Plugin Guide](https://fess.codelibs.org/15.7/admin/plugin-guide.html).
4657

4758
## Usage
4859

49-
Once installed, you can use the "example" script engine in your Fess configuration:
60+
There is **no extra configuration to "use" the engine** — the plugin self-registers via
61+
`fess_se++.xml` when Fess starts. Once installed, the engine is available by its registered name,
62+
`example`, anywhere Fess lets you pick a script type, including:
63+
64+
- **Data store crawling** — field-mapping scripts that compute index field values
65+
- **Document boost** — boost expressions evaluated per document during crawling
66+
- **Scheduled jobs** — jobs whose *Script Type* is set to an engine name
67+
- **Path mappings / replacements** — value transformations that accept a script type
68+
69+
In those places, select or enter `example` as the script type and provide a template such as
70+
`Hello ${name}`. Internally Fess resolves the engine through the factory:
5071

51-
```xml
52-
<component name="exampleScriptEngine" class="org.codelibs.fess.script.example.ExampleEngine"/>
72+
```java
73+
ComponentUtil.getScriptEngineFactory().getScriptEngine("example").evaluate(template, paramMap);
5374
```
5475

55-
The engine will process templates by returning them unchanged, making it useful for:
56-
- Testing script engine integration
57-
- Learning how to implement custom script engines
58-
- As a starting point for more complex implementations
76+
This example engine substitutes `${key}` placeholders with values from the parameter map, which
77+
makes it a useful starting point for learning the API and for building richer engines.
5978

6079
## Development
6180

@@ -73,12 +92,11 @@ mvn clean package
7392
mvn test
7493
```
7594

76-
The test suite includes 19 comprehensive test cases covering:
77-
- Basic functionality
78-
- Edge cases (null/empty inputs)
79-
- Various data types and special characters
80-
- Multi-line templates and large content
81-
- Instance independence and data integrity
95+
The test suite includes focused test cases covering:
96+
- Placeholder substitution (single, multiple, and non-string values)
97+
- Missing-key and null-value behavior (placeholder left untouched)
98+
- Blank/null template handling (returns `null`)
99+
- Engine name and factory registration/lookup by name
82100

83101
### Code Quality
84102

@@ -99,12 +117,16 @@ mvn javadoc:javadoc
99117
src/
100118
├── main/java/
101119
│ └── org/codelibs/fess/script/example/
102-
│ └── ExampleEngine.java # Main script engine implementation
120+
│ └── ExampleEngine.java # Script engine implementation (${key} substitution)
103121
├── main/resources/
104-
│ └── fess_se++.xml # DI container configuration
105-
└── test/java/
106-
└── org/codelibs/fess/script/example/
107-
└── ExampleEngineTest.java # Comprehensive test suite
122+
│ └── fess_se++.xml # DI fragment that registers the engine (additive merge)
123+
└── test/
124+
├── java/
125+
│ └── org/codelibs/fess/script/example/
126+
│ ├── ExampleEngineTest.java # Engine tests + factory lookup
127+
│ └── UnitScriptTestCase.java # Minimal UTFlute test base for the plugin
128+
└── resources/
129+
└── test_app.xml # Test DI container (includes scriptEngineFactory)
108130
```
109131

110132
## Creating Your Own Script Engine

src/main/java/org/codelibs/fess/script/example/ExampleEngine.java

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,36 @@
1515
*/
1616
package org.codelibs.fess.script.example;
1717

18+
import java.util.Collections;
1819
import java.util.Map;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
1922

23+
import org.codelibs.core.lang.StringUtil;
2024
import org.codelibs.fess.script.AbstractScriptEngine;
2125

2226
/**
23-
* Example script engine implementation that demonstrates how to create custom script engines for Fess.
24-
* This implementation simply returns the template string unchanged without any processing.
27+
* Example script engine that demonstrates how to implement a custom script engine for Fess.
28+
*
29+
* <p>A script engine takes a {@code template} string plus a {@code paramMap} of named values
30+
* and produces a result. Fess invokes engines through {@link AbstractScriptEngine}: this example
31+
* registers itself under the name {@code "example"} (see {@link #getName()}) and is wired into the
32+
* DI container by {@code fess_se++.xml}.</p>
33+
*
34+
* <p>To keep the example easy to follow, this engine performs simple {@code ${key}} placeholder
35+
* substitution: every {@code ${key}} occurrence in the template is replaced with the matching
36+
* value from {@code paramMap}. For example, the template {@code "Hello ${name}"} with
37+
* {@code {name=Fess}} evaluates to {@code "Hello Fess"}.</p>
38+
*
39+
* <p>When you build your own engine, the two things you customize are the evaluation logic in
40+
* {@link #evaluate(String, Map)} and the engine identifier in {@link #getName()}. The real
41+
* {@code GroovyEngine} in Fess follows the same shape but evaluates full Groovy scripts.</p>
2542
*/
2643
public class ExampleEngine extends AbstractScriptEngine {
2744

45+
/** Matches {@code ${key}} placeholders where {@code key} is one or more non-"}" characters. */
46+
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
47+
2848
/**
2949
* Creates a new instance of ExampleEngine.
3050
*/
@@ -33,21 +53,50 @@ public ExampleEngine() {
3353
}
3454

3555
/**
36-
* Evaluates the given template with the provided parameter map.
37-
* In this example implementation, the template is returned unchanged without any processing.
56+
* Evaluates the given template by substituting {@code ${key}} placeholders with values
57+
* from the parameter map.
58+
*
59+
* <p>Null-safety mirrors {@code GroovyEngine}: a blank template returns {@code null}, and a
60+
* {@code null} parameter map is treated as an empty map. A {@code ${key}} whose key is missing
61+
* from the map (or maps to {@code null}) is left untouched in the output, so unresolved
62+
* placeholders remain visible rather than being silently dropped.</p>
3863
*
39-
* @param template the template string to evaluate
40-
* @param paramMap the parameter map containing variables for template evaluation
41-
* @return the template string unchanged
64+
* <p>CUSTOMIZE HERE: replace this substitution logic with whatever evaluation your engine
65+
* needs (a real scripting language, an expression evaluator, an external template engine,
66+
* etc.).</p>
67+
*
68+
* @param template the template string to evaluate (null-safe, returns null if blank)
69+
* @param paramMap the parameters available to the template (null-safe, treated as empty if null)
70+
* @return the evaluated string, or null if the template is blank
4271
*/
4372
@Override
4473
public Object evaluate(final String template, final Map<String, Object> paramMap) {
45-
return template;
74+
if (StringUtil.isBlank(template)) {
75+
return null;
76+
}
77+
78+
final Map<String, Object> safeParamMap = paramMap != null ? paramMap : Collections.emptyMap();
79+
80+
final Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);
81+
final StringBuilder buffer = new StringBuilder();
82+
while (matcher.find()) {
83+
final String key = matcher.group(1);
84+
final Object value = safeParamMap.get(key);
85+
// Leave the original "${key}" in place when the key is missing or null.
86+
final String replacement = value != null ? value.toString() : matcher.group();
87+
matcher.appendReplacement(buffer, Matcher.quoteReplacement(replacement));
88+
}
89+
matcher.appendTail(buffer);
90+
return buffer.toString();
4691
}
4792

4893
/**
4994
* Returns the name of this script engine.
5095
*
96+
* <p>CUSTOMIZE HERE: this is the identifier the engine is registered and looked up under
97+
* (e.g. via {@code ScriptEngineFactory.getScriptEngine("example")}). Change it to your own
98+
* engine's unique name.</p>
99+
*
51100
* @return the name "example" that identifies this script engine
52101
*/
53102
@Override

src/main/resources/fess_se++.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!DOCTYPE components PUBLIC "-//DBFLUTE//DTD LastaDi 1.0//EN"
33
"http://dbflute.org/meta/lastadi10.dtd">
4+
<!--
5+
The "++" suffix marks this as an additive-merge fragment: LastaDi merges its
6+
components into the existing fess_se.xml definitions instead of replacing them,
7+
so this plugin can contribute a new engine without redefining the factory.
8+
9+
The "register" postConstruct calls AbstractScriptEngine#register(), which adds
10+
this engine to the scriptEngineFactory under ExampleEngine#getName() ("example").
11+
-->
412
<components>
513
<component name="exampleEngine"
614
class="org.codelibs.fess.script.example.ExampleEngine">

0 commit comments

Comments
 (0)