Skip to content

fix(pathmap): leave the URL unchanged when a script mapping has no engine - #3452

Merged
marevol merged 1 commit into
mainfrom
fix/path-mapping-plain-replacement
Sep 15, 2026
Merged

marevol merged 1 commit into
mainfrom
fix/path-mapping-plain-replacement

Conversation

@marevol

@marevol marevol commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Found while verifying the 15.8 → 15.9 upgrade path. On an installation upgraded without fess-script-groovy, a path mapping with a Groovy replacement such as

groovy:"http://mapped.invalid/en-${matcher.group(1)}"

maps nothing, and the log says something else happened:

WARN  No script engine is registered for groovy, so "groovy:..." is used as a plain replacement. Install the plugin providing it, such as fess-script-groovy for groovy.
WARN  Failed to apply http://example.com/docs/en/(.*) to http://example.com/docs/en/intro.html.
java.lang.IllegalArgumentException: named capturing group is missing trailing '}'

When the engine is missing, PathMappingHelper#resolveEngineMatcher falls back to m.replaceAll(replacement). Matcher#replaceAll reads its argument as a regular-expression replacement, so the ${...} of a GString is taken as a named-group reference and the call throws. PathMapping#process catches the exception, logs it with its stack trace and returns the URL unchanged, and it does so on every call: once per matching search result for a display mapping, and once per matching document for a crawl-time one. The "plain replacement" the first warning announces never happens.

Intended behaviour of the fallback

Group references in that fallback are intended, but not for scripts:

  • 15.8.0 sent every replacement except a groovy: one through Matcher#replaceAll, and pathmap-guide.rst describes a replacement whose prefix is not a registered engine (https://…) as a plain regular-expression replacement. $1 has always worked there.
  • In 15.8.0 a groovy: replacement was always evaluated as a script and never went through replaceAll. Falling back to a plain replacement when its engine is missing is new in 15.9 (feat(script)!: default to JavaScript and treat an unset script type as Groovy #3345).

Change

  • If a replacement's prefix is in SCRIPT_ENGINE_NAMES (groovy, javascript) and that engine is not registered, the mapping is not applied: its matcher function returns the URL unchanged. The warning is still logged once per mapping, and now says the mapping is not applied.
  • Every other prefix still goes through Matcher#replaceAll, $1 included.

The other option was to insert the script literally. That also stops the exception, but every matching URL would become the script text, and a crawl-time mapping would index documents under it. Leaving the URL alone is where 15.9 already ends up after the exception, just without a stack trace per URL. It is also how a document boost rule behaves without its engine: it boosts nothing (#3412). Installing fess-script-groovy makes such a mapping work as it did in 15.8.

Compatibility

test_replaceUrl_missingGroovyEngineIsPlainReplacement pinned the old result for groovy:url without an engine (groovy:urla.html). It is renamed to test_replaceUrl_missingGroovyEngineLeavesUrlUnchanged and now expects the URL unchanged. Mappings whose prefix is not groovy: or javascript: behave exactly as before.

Verification

New tests in PathMappingHelperTest:

  • test_createPathMatcher_missingGroovyEngineDoesNotThrowOnScript: builds the matcher for groovy:"http://mapped.invalid/en-${matcher.group(1)}" with no Groovy engine and applies it. On main this throws IllegalArgumentException: named capturing group is missing trailing '}'; with this change it returns the URL.
  • test_replaceUrl_missingGroovyEngineScriptLeavesUrlAndLogsNoFailure: runs two URLs through replaceUrl. Both come back unchanged, PathMapping logs no warning, and PathMappingHelper logs exactly one, naming fess-script-groovy. On main it fails on the Failed to apply … IllegalArgumentException warnings.
  • test_replaceUrl_unregisteredPrefixKeepsGroupReferences: https://files.example.com/$1 still substitutes the group. It passes before and after and guards the intended behaviour.

mvn test -Dtest=PathMappingHelperTest: 40 tests. On main 1 failure and 1 error (the two new tests); on this branch 0 failures and 0 errors.

mvn test: 7490 tests, 0 failures, 0 errors.

…gine

A path mapping whose replacement names a script engine that is not registered,
typically a groovy: mapping saved before 15.9 on an installation without
fess-script-groovy, logs that the replacement "is used as a plain replacement"
and then fails on every URL it matches. The fallback in
PathMappingHelper#resolveEngineMatcher hands the whole replacement to
Matcher#replaceAll, which reads it as a regular-expression replacement: the
${...} of a Groovy GString is a named-group reference, so the call throws
IllegalArgumentException ("named capturing group is missing trailing '}'").
PathMapping#process catches it, logs a warning with the stack trace and returns
the URL unchanged, on every call. The replacement the first warning announces
never happens.

Group references in that fallback are intended, but not for scripts. 15.8.0
sent every replacement except a groovy: one through Matcher#replaceAll, and the
path mapping guide describes a replacement whose prefix is not a registered
engine, such as https://, as a plain regular-expression replacement. That path
is unchanged, $1 included.

A replacement whose prefix is one of the script types Fess records (groovy,
javascript) is script text, and neither reading of it gives a mapping anyone
wrote: as a regular-expression replacement it throws on ${...}, and inserted
literally it turns every matching URL into the script, which a crawl-time
mapping would then index. Such a mapping is now not applied. The URL is left as
it is, and the missing engine is still reported once per mapping, with the
warning saying the mapping is not applied. A document boost rule without its
engine already behaves the same way and boosts nothing.

test_replaceUrl_missingGroovyEngineIsPlainReplacement pinned the literal result
(groovy:url becoming groovy:urla.html); it is renamed to
test_replaceUrl_missingGroovyEngineLeavesUrlUnchanged and expects the URL
unchanged.
@marevol marevol added this to the 15.9.0 milestone Sep 15, 2026
@marevol marevol self-assigned this Sep 15, 2026
@marevol
marevol merged commit a64f1da into main Sep 15, 2026
2 checks passed
@marevol
marevol deleted the fix/path-mapping-plain-replacement branch September 16, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant