From a807fd6487f4fcb3bc4c5126e7bc7bde885dd226 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Tue, 15 Sep 2026 23:19:34 +0900 Subject: [PATCH] fix(script): warn at startup about settings that use a missing script engine An installation upgraded from 15.8 keeps groovy wherever it is stored, and Groovy ships as the fess-script-groovy plugin since 15.9. The startup bulk load of fess_config.scheduled_job only creates jobs that do not exist yet, so all 14 bundled jobs keep script_type=groovy, and a crawl config, data config or document boost rule saved before 15.9 has no script type, which also means groovy. Without the plugin each of them fails, but only when it runs, and nothing at startup says so: - A groovy job fails whenever it runs with "groovy is not found". Nine of the bundled jobs have job logging off, so their only trace is a "Failed to execute job" warning in fess.log. - A web or file config with a field script indexes no documents while its crawl job reports ok; the documents end up as failure URLs with a ScriptEngineException. - A document boost rule boosts nothing and a groovy: path mapping maps nothing. Both are reported, but only where and when they are first used. MissingScriptEngineReporter runs from AllJobScheduler#schedule. By then the DI container has started, so every script engine plugin has registered, and no job has run yet. The scheduler is started by LastaPrepareFilter, so this happens in the web application only, not in the crawler or the other child processes. The reporter reads the scheduled jobs, web, file and data configs, document boost rules and path mappings, resolves the script type each one runs with, counting an unset type as groovy just as the runtime does, and logs one warning per engine that has no registered ScriptEngine. For each kind of setting the warning gives a count and up to three names (ids for boost rules and path mappings), names the plugin in the wording ScriptEngineFactory already uses, and says where that kind takes javascript instead. Only settings that evaluate a script are counted: crawl configs with a field.script.* parameter, data configs with a handler script, and path mappings whose prefix is groovy: or javascript:, the prefixes PathMappingHelper treats as scripts. job.default.script is reported too: a 15.8 fess_config.properties copied during the upgrade still sets it to groovy, and the scheduler's create form offers it as the default for new jobs. The check is one query per kind against the fess_config indices, bounded by the existing page.*.max.fetch.size settings, and it never throws: a kind that cannot be read is skipped and logged at debug. --- .../fess/app/job/AllJobScheduler.java | 3 + .../helper/MissingScriptEngineReporter.java | 276 ++++++++++++++++ .../MissingScriptEngineReporterTest.java | 297 ++++++++++++++++++ 3 files changed, 576 insertions(+) create mode 100644 src/main/java/org/codelibs/fess/helper/MissingScriptEngineReporter.java create mode 100644 src/test/java/org/codelibs/fess/helper/MissingScriptEngineReporterTest.java diff --git a/src/main/java/org/codelibs/fess/app/job/AllJobScheduler.java b/src/main/java/org/codelibs/fess/app/job/AllJobScheduler.java index 26a703bfe..a31da307a 100644 --- a/src/main/java/org/codelibs/fess/app/job/AllJobScheduler.java +++ b/src/main/java/org/codelibs/fess/app/job/AllJobScheduler.java @@ -23,6 +23,7 @@ import org.codelibs.fess.app.logic.AccessContextLogic; import org.codelibs.fess.app.service.ScheduledJobService; import org.codelibs.fess.helper.JobHelper; +import org.codelibs.fess.helper.MissingScriptEngineReporter; import org.codelibs.fess.helper.SystemHelper; import org.codelibs.fess.mylasta.direction.FessConfig; import org.codelibs.fess.opensearch.config.exbhv.JobLogBhv; @@ -82,6 +83,8 @@ public AllJobScheduler() { public void schedule(final LaCron cron) { schedulerTime = systemHelper.getCurrentTimeAsLong(); scheduledJobService.start(cron); + // Every script engine plugin has registered by now, and nothing has run yet. + new MissingScriptEngineReporter().report(); final String myName = fessConfig.getSchedulerTargetName(); if (StringUtil.isNotBlank(myName)) { diff --git a/src/main/java/org/codelibs/fess/helper/MissingScriptEngineReporter.java b/src/main/java/org/codelibs/fess/helper/MissingScriptEngineReporter.java new file mode 100644 index 000000000..00ea3ad46 --- /dev/null +++ b/src/main/java/org/codelibs/fess/helper/MissingScriptEngineReporter.java @@ -0,0 +1,276 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.codelibs.fess.helper; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.Constants; +import org.codelibs.fess.app.service.ScheduledJobService; +import org.codelibs.fess.indexer.DocBoostMatcher; +import org.codelibs.fess.opensearch.config.exbhv.BoostDocumentRuleBhv; +import org.codelibs.fess.opensearch.config.exbhv.PathMappingBhv; +import org.codelibs.fess.opensearch.config.exentity.BoostDocumentRule; +import org.codelibs.fess.opensearch.config.exentity.CrawlingConfig; +import org.codelibs.fess.opensearch.config.exentity.CrawlingConfig.ConfigName; +import org.codelibs.fess.opensearch.config.exentity.DataConfig; +import org.codelibs.fess.opensearch.config.exentity.FileConfig; +import org.codelibs.fess.opensearch.config.exentity.PathMapping; +import org.codelibs.fess.opensearch.config.exentity.ScheduledJob; +import org.codelibs.fess.opensearch.config.exentity.WebConfig; +import org.codelibs.fess.script.ScriptEngineFactory; +import org.codelibs.fess.util.ComponentUtil; + +/** + * Warns about stored settings that name a script engine no plugin has registered. + * + *

An upgrade does not rewrite what is stored in the index, and a script type that was never + * set still means groovy. Since Groovy moved to the fess-script-groovy plugin, a 15.8 installation + * upgraded without it keeps groovy on every scheduled job - the bundled ones too, because the + * startup bulk load only creates documents that do not exist yet - and on every crawl config, + * data config and document boost rule saved before 15.9. Each of those fails only when it runs, + * and mostly where nobody looks first: a job with job logging off leaves a warning in fess.log and + * nothing in the job log, and a crawl config whose field scripts cannot be evaluated indexes no + * documents while its job reports ok. One warning per engine at startup names them together.

+ */ +public class MissingScriptEngineReporter { + + private static final Logger logger = LogManager.getLogger(MissingScriptEngineReporter.class); + + /** How many names of one kind of setting a warning lists. */ + protected static final int MAX_LISTED_NAMES = 3; + + /** The handler parameter a data config names its script type with; see AbstractDataStore#getScriptType. */ + protected static final String DATA_CONFIG_SCRIPT_TYPE = "script_type"; + + /** + * Default constructor. + */ + public MissingScriptEngineReporter() { + // nothing + } + + /** + * Logs one warning for each script engine that stored settings use and no plugin registers. + * + *

This runs while the application starts, so it never throws: a setting that cannot be + * read is skipped and the failure is logged at debug.

+ */ + public void report() { + try { + final ScriptEngineFactory scriptEngineFactory = ComponentUtil.getScriptEngineFactory(); + collect(scriptEngineFactory).forEach((engine, settings) -> { + logger.warn("Settings use the script engine {}, which is not registered, so their scripts cannot run: {}." + + " A setting without a script type uses groovy. A plugin may be missing, such as fess-script-groovy for groovy." + + " To use JavaScript instead, rewrite each script and set javascript as its script type: the Execution Method" + + " of a scheduled job, the Script Type of a document boost rule, config.script.type=javascript in the" + + " Config Parameter of a web or file config, script_type=javascript in the Parameter of a data config," + + " the javascript: prefix of a path mapping Replacement, and job.default.script in fess_config.properties.", + engine, summarize(settings)); + }); + } catch (final Exception e) { + logger.debug("Failed to check the script engines used by stored settings.", e); + } + } + + /** + * Finds the stored settings whose script type has no registered engine. + * + * @param scriptEngineFactory the factory the engines are registered with + * @return the names of those settings, by lower-cased engine name and then by kind of setting + */ + protected Map>> collect(final ScriptEngineFactory scriptEngineFactory) { + final Map>> usage = new TreeMap<>(); + collect(usage, scriptEngineFactory, "scheduled jobs", this::loadScheduledJobs, ScheduledJob::getScriptType, ScheduledJob::getName); + collect(usage, scriptEngineFactory, "web configs", this::loadWebConfigs, this::getFieldScriptType, WebConfig::getName); + collect(usage, scriptEngineFactory, "file configs", this::loadFileConfigs, this::getFieldScriptType, FileConfig::getName); + collect(usage, scriptEngineFactory, "data configs", this::loadDataConfigs, this::getHandlerScriptType, DataConfig::getName); + collect(usage, scriptEngineFactory, "document boost rules", this::loadBoostDocumentRules, + rule -> new DocBoostMatcher(rule).getScriptType(), BoostDocumentRule::getId); + collect(usage, scriptEngineFactory, "path mappings", this::loadPathMappings, this::getReplacementScriptType, PathMapping::getId); + collect(usage, scriptEngineFactory, "properties", () -> List.of("job.default.script"), key -> loadJobDefaultScript(), + Function.identity()); + return usage; + } + + /** + * Adds the settings of one kind whose script type has no registered engine. + * + * @param the type of setting + * @param usage the result to add to + * @param scriptEngineFactory the factory the engines are registered with + * @param kind the kind of setting, as the warning names it + * @param loader loads the settings + * @param scriptTypeOf the script type a setting runs its scripts with, or null when it has none + * @param nameOf the name a setting is listed by + */ + protected void collect(final Map>> usage, final ScriptEngineFactory scriptEngineFactory, + final String kind, final Supplier> loader, final Function scriptTypeOf, final Function nameOf) { + try { + for (final T setting : loader.get()) { + final String scriptType = scriptTypeOf.apply(setting); + if (StringUtil.isBlank(scriptType) || scriptEngineFactory.hasScriptEngine(scriptType)) { + continue; + } + usage.computeIfAbsent(scriptType.toLowerCase(Locale.ROOT), k -> new LinkedHashMap<>()) + .computeIfAbsent(kind, k -> new ArrayList<>()) + .add(nameOf.apply(setting)); + } + } catch (final Exception e) { + logger.debug("Failed to check the script engines used by {}.", kind, e); + } + } + + /** + * Formats the settings of one engine as {@code kind=count [name, name, name, ...]}. + * + * @param settings the names of the settings by kind + * @return the summary + */ + protected String summarize(final Map> settings) { + return settings.entrySet().stream().map(e -> { + final List names = e.getValue(); + final String listed = names.stream().limit(MAX_LISTED_NAMES).collect(Collectors.joining(", ")); + return e.getKey() + "=" + names.size() + " [" + listed + (names.size() > MAX_LISTED_NAMES ? ", ..." : "") + "]"; + }).collect(Collectors.joining(", ")); + } + + /** + * Gets the script type a web or file config evaluates its field scripts with. + * + * @param config the crawl config + * @return the script type, or null when the config has no field script + */ + protected String getFieldScriptType(final CrawlingConfig config) { + if (config.getConfigParameterMap(ConfigName.SCRIPT).isEmpty()) { + return null; + } + return config.getScriptType(); + } + + /** + * Gets the script type a data config evaluates its handler script with. + * + * @param config the data config + * @return the script type, or null when the config has no handler script + */ + protected String getHandlerScriptType(final DataConfig config) { + if (config.getHandlerScriptMap().isEmpty()) { + return null; + } + final String scriptType = config.getHandlerParameterMap().get(DATA_CONFIG_SCRIPT_TYPE); + return StringUtil.isBlank(scriptType) ? Constants.LEGACY_SCRIPT : scriptType; + } + + /** + * Gets the script type a path mapping replacement names with its prefix. + * + * @param pathMapping the path mapping + * @return the script type, or null when the replacement is not a script + */ + protected String getReplacementScriptType(final PathMapping pathMapping) { + final String replacement = pathMapping.getReplacement(); + if (replacement == null) { + return null; + } + final int separatorIndex = replacement.indexOf(':'); + if (separatorIndex <= 0) { + return null; + } + final String prefix = replacement.substring(0, separatorIndex); + // Any other prefix, such as https:, is an ordinary replacement; see PathMappingHelper. + return PathMappingHelper.SCRIPT_ENGINE_NAMES.contains(prefix.toLowerCase(Locale.ROOT)) ? prefix : null; + } + + /** + * Loads the scheduled jobs. + * + * @return the scheduled jobs + */ + protected List loadScheduledJobs() { + return ComponentUtil.getComponent(ScheduledJobService.class).getScheduledJobList(); + } + + /** + * Loads the web configs, including disabled ones. + * + * @return the web configs + */ + protected List loadWebConfigs() { + return ComponentUtil.getCrawlingConfigHelper().getAllWebConfigList(false, false, false, null); + } + + /** + * Loads the file configs, including disabled ones. + * + * @return the file configs + */ + protected List loadFileConfigs() { + return ComponentUtil.getCrawlingConfigHelper().getAllFileConfigList(false, false, false, null); + } + + /** + * Loads the data configs, including disabled ones. + * + * @return the data configs + */ + protected List loadDataConfigs() { + return ComponentUtil.getCrawlingConfigHelper().getAllDataConfigList(false, false, false, null); + } + + /** + * Loads the document boost rules. + * + * @return the document boost rules + */ + protected List loadBoostDocumentRules() { + return ComponentUtil.getComponent(BoostDocumentRuleBhv.class).selectList(cb -> { + cb.query().matchAll(); + cb.fetchFirst(ComponentUtil.getFessConfig().getPageDocboostMaxFetchSizeAsInteger()); + }); + } + + /** + * Loads the path mappings of every process type. + * + * @return the path mappings + */ + protected List loadPathMappings() { + return ComponentUtil.getComponent(PathMappingBhv.class).selectList(cb -> { + cb.query().matchAll(); + cb.fetchFirst(ComponentUtil.getFessConfig().getPagePathMappingMaxFetchSizeAsInteger()); + }); + } + + /** + * Loads the script type the scheduler's create form offers a new job. + * + * @return the value of job.default.script + */ + protected String loadJobDefaultScript() { + return ComponentUtil.getFessConfig().getJobDefaultScript(); + } +} diff --git a/src/test/java/org/codelibs/fess/helper/MissingScriptEngineReporterTest.java b/src/test/java/org/codelibs/fess/helper/MissingScriptEngineReporterTest.java new file mode 100644 index 000000000..7b0b214e0 --- /dev/null +++ b/src/test/java/org/codelibs/fess/helper/MissingScriptEngineReporterTest.java @@ -0,0 +1,297 @@ +/* + * Copyright 2012-2025 CodeLibs Project and the Others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package org.codelibs.fess.helper; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.codelibs.fess.mylasta.direction.FessConfig; +import org.codelibs.fess.mylasta.direction.FessProp; +import org.codelibs.fess.opensearch.config.exentity.BoostDocumentRule; +import org.codelibs.fess.opensearch.config.exentity.DataConfig; +import org.codelibs.fess.opensearch.config.exentity.FileConfig; +import org.codelibs.fess.opensearch.config.exentity.PathMapping; +import org.codelibs.fess.opensearch.config.exentity.ScheduledJob; +import org.codelibs.fess.opensearch.config.exentity.WebConfig; +import org.codelibs.fess.script.ScriptEngineFactory; +import org.codelibs.fess.unit.LogCapturingAppender; +import org.codelibs.fess.unit.UnitFessTestCase; +import org.codelibs.fess.util.ComponentUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class MissingScriptEngineReporterTest extends UnitFessTestCase { + + private ScriptEngineFactory scriptEngineFactory; + + @Override + protected void setUp(final TestInfo testInfo) throws Exception { + super.setUp(testInfo); + FessProp.propMap.clear(); + ComponentUtil.setFessConfig(new FessConfig.SimpleImpl() { + @Override + public String getAppEncryptPropertyPattern() { + return ".*password|.*key"; + } + }); + scriptEngineFactory = new ScriptEngineFactory(); + scriptEngineFactory.add("javascript", (template, paramMap) -> null); + ComponentUtil.register(scriptEngineFactory, "scriptEngineFactory"); + } + + @Test + public void test_collect_findsSettingsWhoseEngineIsNotRegistered() { + final StubReporter reporter = new StubReporter(); + reporter.jobs.add(job("Default Crawler", "groovy")); + reporter.jobs.add(job("Unset Job", null)); + reporter.jobs.add(job("JavaScript Job", "javascript")); + reporter.webConfigs.add(webConfig("legacy-web", "field.xpath.title=//TITLE\nfield.script.title=def t = value; return t")); + reporter.webConfigs.add(webConfig("no-script-web", "field.xpath.title=//TITLE")); + reporter.webConfigs.add(webConfig("js-web", "config.script.type=javascript\nfield.script.title=value")); + reporter.fileConfigs.add(fileConfig("legacy-file", "config.script.type=groovy\nfield.script.title=value")); + reporter.dataConfigs.add(dataConfig("legacy-data", null, "url=\"http://example.com/\" + id")); + reporter.dataConfigs.add(dataConfig("js-data", "script_type=javascript", "url=\"http://example.com/\" + id")); + reporter.dataConfigs.add(dataConfig("no-script-data", null, null)); + reporter.boostRules.add(boostRule("rule1", null)); + reporter.boostRules.add(boostRule("rule2", "javascript")); + reporter.pathMappings.add(pathMapping("map1", "groovy:\"http://mapped.invalid/${matcher.group(1)}\"")); + reporter.pathMappings.add(pathMapping("map2", "https://example.net/$1")); + reporter.pathMappings.add(pathMapping("map3", "javascript:url")); + reporter.pathMappings.add(pathMapping("map4", "function:encodeUrl")); + reporter.jobDefaultScript = "groovy"; + + final Map> expected = new LinkedHashMap<>(); + expected.put("scheduled jobs", List.of("Default Crawler", "Unset Job")); + expected.put("web configs", List.of("legacy-web")); + expected.put("file configs", List.of("legacy-file")); + expected.put("data configs", List.of("legacy-data")); + expected.put("document boost rules", List.of("rule1")); + expected.put("path mappings", List.of("map1")); + expected.put("properties", List.of("job.default.script")); + assertEquals(Map.of("groovy", expected), reporter.collect(scriptEngineFactory)); + } + + @Test + public void test_collect_isEmptyWhenEveryEngineIsRegistered() { + scriptEngineFactory.add("groovy", (template, paramMap) -> null); + final StubReporter reporter = new StubReporter(); + reporter.jobs.add(job("Default Crawler", "groovy")); + reporter.webConfigs.add(webConfig("legacy-web", "field.script.title=value")); + reporter.boostRules.add(boostRule("rule1", "")); + reporter.pathMappings.add(pathMapping("map1", "groovy:url")); + reporter.jobDefaultScript = "groovy"; + + assertTrue(reporter.collect(scriptEngineFactory).isEmpty()); + } + + @Test + public void test_collect_groupsByEngineName() { + final StubReporter reporter = new StubReporter(); + reporter.jobs.add(job("A", "ognl")); + reporter.jobs.add(job("B", "Groovy")); + reporter.jobs.add(job("C", "groovy")); + + final Map>> usage = reporter.collect(scriptEngineFactory); + assertEquals(List.of("groovy", "ognl"), new ArrayList<>(usage.keySet())); + assertEquals(List.of("B", "C"), usage.get("groovy").get("scheduled jobs")); + assertEquals(List.of("A"), usage.get("ognl").get("scheduled jobs")); + } + + @Test + public void test_summarize() { + final Map> settings = new LinkedHashMap<>(); + settings.put("scheduled jobs", List.of("Default Crawler", "Suggest Indexer", "Log Aggregator", "Doc Purger", "Log Purger")); + settings.put("web configs", List.of("legacy-web")); + settings.put("properties", List.of("job.default.script")); + + assertEquals("scheduled jobs=5 [Default Crawler, Suggest Indexer, Log Aggregator, ...], web configs=1 [legacy-web]," + + " properties=1 [job.default.script]", new MissingScriptEngineReporter().summarize(settings)); + } + + @Test + public void test_report_warnsOncePerEngine() { + final StubReporter reporter = new StubReporter(); + reporter.jobs.add(job("Default Crawler", "groovy")); + reporter.jobs.add(job("Suggest Indexer", "groovy")); + reporter.jobs.add(job("OGNL Job", "ognl")); + reporter.boostRules.add(boostRule("rule1", null)); + + final LogCapturingAppender appender = LogCapturingAppender.attach(MissingScriptEngineReporter.class); + try { + reporter.report(); + final List warnings = appender.warnings(); + assertEquals(2, warnings.size()); + assertTrue(warnings.get(0).startsWith("Settings use the script engine groovy, which is not registered,"), warnings.get(0)); + assertTrue(warnings.get(0).contains("scheduled jobs=2 [Default Crawler, Suggest Indexer], document boost rules=1 [rule1]."), + warnings.get(0)); + assertTrue(warnings.get(0).contains("A plugin may be missing, such as fess-script-groovy for groovy."), warnings.get(0)); + assertTrue(warnings.get(0).contains("set javascript as its script type"), warnings.get(0)); + assertTrue(warnings.get(1).startsWith("Settings use the script engine ognl, which is not registered,"), warnings.get(1)); + assertTrue(warnings.get(1).contains("scheduled jobs=1 [OGNL Job]."), warnings.get(1)); + } finally { + appender.detach(); + } + } + + @Test + public void test_report_isSilentWhenNothingIsMissing() { + final StubReporter reporter = new StubReporter(); + reporter.jobs.add(job("Default Crawler", "javascript")); + reporter.webConfigs.add(webConfig("legacy-web", "field.xpath.title=//TITLE")); + + final LogCapturingAppender appender = LogCapturingAppender.attach(MissingScriptEngineReporter.class); + try { + reporter.report(); + assertTrue(appender.warnings().isEmpty(), appender.renderedEvents().toString()); + } finally { + appender.detach(); + } + } + + @Test + public void test_report_skipsSettingsThatCannotBeLoaded() { + final StubReporter reporter = new StubReporter() { + @Override + protected List loadWebConfigs() { + throw new IllegalStateException("fess_config.web_config is unavailable"); + } + }; + reporter.jobs.add(job("Default Crawler", "groovy")); + + final LogCapturingAppender appender = LogCapturingAppender.attach(MissingScriptEngineReporter.class); + try { + reporter.report(); + assertEquals(1, appender.warnings().size()); + assertTrue(appender.warnings().get(0).contains("scheduled jobs=1 [Default Crawler]."), appender.warnings().get(0)); + assertFalse(appender.warnings().get(0).contains("web configs"), appender.warnings().get(0)); + } finally { + appender.detach(); + } + } + + @Test + public void test_report_neverThrows() { + ComponentUtil.register(new ScriptEngineFactory() { + @Override + public boolean hasScriptEngine(final String name) { + throw new IllegalStateException("no script engine registry"); + } + }, "scriptEngineFactory"); + final StubReporter reporter = new StubReporter(); + reporter.jobs.add(job("Default Crawler", "groovy")); + + final LogCapturingAppender appender = LogCapturingAppender.attach(MissingScriptEngineReporter.class); + try { + reporter.report(); + assertTrue(appender.warnings().isEmpty(), appender.renderedEvents().toString()); + } finally { + appender.detach(); + } + } + + private static ScheduledJob job(final String name, final String scriptType) { + final ScheduledJob job = new ScheduledJob(); + job.setName(name); + job.setScriptType(scriptType); + return job; + } + + private static WebConfig webConfig(final String name, final String configParameter) { + final WebConfig config = new WebConfig(); + config.setName(name); + config.setConfigParameter(configParameter); + return config; + } + + private static FileConfig fileConfig(final String name, final String configParameter) { + final FileConfig config = new FileConfig(); + config.setName(name); + config.setConfigParameter(configParameter); + return config; + } + + private static DataConfig dataConfig(final String name, final String handlerParameter, final String handlerScript) { + final DataConfig config = new DataConfig(); + config.setName(name); + config.setHandlerParameter(handlerParameter); + config.setHandlerScript(handlerScript); + return config; + } + + private static BoostDocumentRule boostRule(final String id, final String scriptType) { + final BoostDocumentRule rule = new BoostDocumentRule(); + rule.setId(id); + rule.setUrlExpr("url.matches(\".*/docs/ja/.*\")"); + rule.setBoostExpr("7.0"); + rule.setScriptType(scriptType); + return rule; + } + + private static PathMapping pathMapping(final String id, final String replacement) { + final PathMapping pathMapping = new PathMapping(); + pathMapping.setId(id); + pathMapping.setRegex("http://localhost/docs/(.*)"); + pathMapping.setReplacement(replacement); + return pathMapping; + } + + private static class StubReporter extends MissingScriptEngineReporter { + final List jobs = new ArrayList<>(); + final List webConfigs = new ArrayList<>(); + final List fileConfigs = new ArrayList<>(); + final List dataConfigs = new ArrayList<>(); + final List boostRules = new ArrayList<>(); + final List pathMappings = new ArrayList<>(); + String jobDefaultScript = "javascript"; + + @Override + protected List loadScheduledJobs() { + return jobs; + } + + @Override + protected List loadWebConfigs() { + return webConfigs; + } + + @Override + protected List loadFileConfigs() { + return fileConfigs; + } + + @Override + protected List loadDataConfigs() { + return dataConfigs; + } + + @Override + protected List loadBoostDocumentRules() { + return boostRules; + } + + @Override + protected List loadPathMappings() { + return pathMappings; + } + + @Override + protected String loadJobDefaultScript() { + return jobDefaultScript; + } + } +}