[feature] Add ModuleFactory and IndexFactory SPI for auto-discovery of bundled modules and indexes - #6551
[feature] Add ModuleFactory and IndexFactory SPI for auto-discovery of bundled modules and indexes#6551duncdrum wants to merge 11 commits into
Conversation
1eece7a to
afe8ce0
Compare
line-o
left a comment
There was a problem hiding this comment.
Resolving modules to load by default over SPI received pushback on the community call on 2026-07-06
Security concerns need to be addressed before we can consider pulling this in.
@dizzzz specific concern was: "I see autoloading modules that are not mentioned in the configuration as a risk."
My concern is: if bundled modules are left out of the configuration I have to know they are there in order to disable them again.
|
That said, I had similar ideas re module loading a long time ago. "plug the modules you only need". My original ideas can from a slightly different angle: will this make loading java modules from a XAR file more simple? |
| private static final Logger LOG = LogManager.getLogger(Collection.class); | ||
| private static final int SHALLOW_SIZE = 550; | ||
| private static final int DOCUMENT_SIZE = 450; | ||
| private static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; |
There was a problem hiding this comment.
I'd prefer to use and extend exist-core/src/main/java/org/exist/Namespaces.java om stead of adding it here. JAXP.java had the same definition too, which shall be combined.
There was a problem hiding this comment.
Already addressed upstream — XSD_1_1_NS moved to Namespaces.java and the duplicate JAXP.java definition dropped in 0fe1766a96 (PR-C, #6530). This branch picked that up in today's rebase.
| * attacker-influenced), so unlike {@link org.exist.validation.Xsd11SchemaDetection}'s | ||
| * location-driven cache, no per-Subject scoping/bounding is needed here. | ||
| */ | ||
| private static final ConcurrentMap<String, Optional<Schema>> XSD11_SCHEMA_BY_NAMESPACE = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
Shouldn't we use Cafeine for this, and make a shared/global cache for this? MutableCollection does not look a good place for this?
There was a problem hiding this comment.
Done rather than deferred — split into two package-private classes in af2333f396: Xsd11SchemaCache (Caffeine-backed; no eviction policy, since the namespace set is finite and admin-controlled, which was your own point) holds the cache, and Xsd11ValidationHelper holds resolveXsd11SchemaForNamespace, the dynamic-discovery schema holder, validateWithXsd11Schema, and the lexical-handler forwarder. MutableCollection is down to a one-line delegating call. All 63 tests in the validation/XSD11 suite pass unchanged. This branch already picked up the fix in today's rebase.
| final AbstractIndex index = (AbstractIndex) clazz.newInstance(); | ||
| index.configure(pool, dataDir, config); | ||
| if (index.getIndexName() == null) { | ||
| index.setName(id); |
There was a problem hiding this comment.
what if if==null or "" or blank?
There was a problem hiding this comment.
Handled — id != null && !id.isBlank() covers null, empty, and whitespace-only together; see the guard right above this line (21cd10a8cf, renumbered from 11d1c929d9 by today's rebase).
dizzzz
left a comment
There was a problem hiding this comment.
it looks that this PR combines 2 or 3 PRs (which makes the PR more difficult to review than strictly needed).
|
These are stacked PRs that all target MutableCollection.java:106 (Namespaces.java) MutableCollection.java:116 (Caffeine / separate class) IndexManager.java:146 (null/blank id) JettyStart.java:125 (different PR) On your question about XAR module loading: |
153a184 to
11d1c92
Compare
| for (int i = 0; i < params.getLength(); i++) { | ||
| final Element param = ((Element) params.item(i)); | ||
|
|
||
| if ("no".equalsIgnoreCase(param.getAttribute("enabled"))) { |
There was a problem hiding this comment.
"no" only — @enabled's type in conf.xsd is yes_no, an enumeration restricted to exactly yes/no (not xs:boolean), so "false" isn't a valid value to begin with. All 5 call sites (4 in Configuration.java, this one) check the same literal for that reason.
…f MutableCollection MutableCollection carried a schema cache (plain ConcurrentHashMap) plus the namespace-resolution/validation logic that uses it -- neither is Collection state. Splits this into two package-private classes: - Xsd11SchemaCache: the per-namespace "needs XSD 1.1?" cache, now backed by Caffeine for consistency with the codebase's other caches (no eviction policy, since the namespace set is finite and admin-controlled -- this was never a correctness concern, just a style/testability one). - Xsd11ValidationHelper: resolveXsd11SchemaForNamespace, the dynamic-discovery schema holder, parseOrValidateXmlSource, validateWithXsd11Schema, and the Xsd11LexicalHandlerForwarder record -- store-time XSD 1.1 validation, not Collection bookkeeping. MutableCollection keeps only its public clearXsd11SchemaByNamespaceCache() facade (GrammarTooling.java's external call site) and a single delegating call from storeXmlDocument(); 9 imports that only the moved code needed are dropped. Behavior-preserving: all 63 tests across the validation/XSD11 suite pass unchanged. Addresses dizzzz's review comments on eXist-db#6530/eXist-db#6551.
… stylesheets conf-fixture.xsl and controller-config-fixture.xsl are new eXist-db-authored XSLT codegen tooling, not modifications of Adam Retter's original BSD-3 conf.xml/controller-config.xml. They fell outside the module's existing conf.xml/log4j2.xml excludes and so wrongly inherited the module-wide BSD-3 header. Excludes them from the BSD-3 licenseSet and adds a second licenseSet applying eXist-db's default LGPL-21 header to just these two files instead. Addresses review discussion between dizzzz, adamretter, and reinhapa on eXist-db#6551.
… stylesheets conf-fixture.xsl and controller-config-fixture.xsl are new eXist-db-authored XSLT codegen tooling, not modifications of Adam Retter's original BSD-3 conf.xml/controller-config.xml. They fell outside the module's existing conf.xml/log4j2.xml excludes and so wrongly inherited the module-wide BSD-3 header. Excludes them from the BSD-3 licenseSet and adds a second licenseSet applying eXist-db's default LGPL-21 header to just these two files instead. Addresses review discussion between dizzzz, adamretter, and reinhapa on eXist-db#6551.
795cf70 to
9e0973c
Compare
…X parser features in conf.xml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…yes", scheduler jobs as live enabled="no" entries, @enabled on <parameter> and <property> in conf.xsd; bump schema to 2.5.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…2.6.0 - Add memorySizeType (pattern [0-9]+[KkMmGgTt]?|-1); apply to cacheSize, collectionCache, minDiskSpace, recovery/@SiZe - pool/@min, @max: xs:integer → xs:positiveInteger - lock-table/@trace-stack-depth: xs:int → xs:nonNegativeInteger - indexer/create/@type: xs:anySimpleType → xs:string - transformer/@caching: xs:string → yes_no with default="yes" - scheduler/job/@period: xs:string → xs:positiveInteger - vector-models/model/@Dimension: xs:integer → xs:positiveInteger - Fix swapped docs on raise-error-on-failed-retrieval / enforce-index-use - Fix "group-comit" typo in recovery/@group-commit doc - Fix rpc-server in-memory-size default 4196 → 4096 - Fill in TODO docs: indexer/create attrs, flushAfter, n, xquery module attrs (class/uri/src), xupdate/@growth-factor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ure triggers
ParametersExtractor.parseProperties() used case-sensitive .equals("no") while
every other enabled check in Configuration.java uses .equalsIgnoreCase(). An
enabled="NO" parameter would be silently included instead of skipped.
generate-conf-fixture.xsl stripped @enabled from kept module/index nodes but
not from surviving triggers, so BouncyCastle and URLStreamHandler emitted an
explicit enabled="yes" in generated fixtures — inconsistent with the module
treatment and noise in fixture diffs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nonical conf.xml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…abled to <vector-models> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…canonical conf.xml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…f.xml element
AbstractIndex.configure() only sets name from config.getAttribute("id") when
config != null. IndexFactory SPI registration passes config=null, leaving name
null. IndexController.getWorkerByIndexName() matches on name, so SPI-registered
indexes (range-index, ngram-index, sort-index, lucene-index) were never found,
causing NPE at every range:index-keys-for-field() call.
Fix: after configure(), if name is still null, call setName(id) with the
configuration id that was used to key the indexers map.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…es covered by existing imports Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
factory.getDefaultId() is a third-party contract; a null or blank return would silently register the index under a useless key and leave its name unset. Validate at the Configuration.java SPI loop (primary gate) and add a belt-and-suspenders check in IndexManager.initIndex so the name is never set to null/blank. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9e0973c to
c03d824
Compare
📊 XQTS result comparisonComparison of this run against Warning 167 test cases were recorded in only one of the two runs (9 only in the previous run, 158 only in the current run). The runner's JUnit output is not fully deterministic (see eXist-db/exist-xqts-runner#74), so totals and per-category deltas include recording noise; the newly passing/failing lists count only tests recorded in both runs.
Relative to 🔴 Newly failing tests (1)
⚪ Recorded only in this run (158)
⚪ Recorded only in `develop` (9)
Runtime: 509.0s (+66.11s vs |
Summary
ServiceLoader-based SPI for XQuery modules and index modules, so bundled implementations are auto-discovered without explicitconf.xmlentries. New installs get a leaner default config; existing hand-maintained configs are unaffected — explicit entries always win over SPI, andenabled="no"(from #6550) suppresses an SPI entry without removing the JAR.Closes #3062 — delivers both parts: the
@enabledattribute (#6550) and the SPI/CDI-style autodiscovery this PR adds.What changed
ModuleFactorySPI — neworg.exist.xquery.ModuleFactoryinterface;Configuration.configureModules()scansServiceLoader<ModuleFactory>before theconf.xmlloop. All 27 bundled XQuery modules register viaMETA-INF/services/.IndexFactorySPI — neworg.exist.indexing.IndexFactoryinterface;IndexManagerregisters SPI entries whoseidhas no liveconf.xmlentry. All bundled indexes (Lucene, ngram, range, sort, spatial) wired; indexnamedefaults to the SPIidwhen there's noconf.xmlelement to supply one; guarded against a null/blankidfrom the factory.Vector model registry — integrated with
Configuration;@enabledon<vector-models>children; repeated WARN for unavailable models downgraded to DEBUG after the first occurrence.exist-distribution/src/main/config/conf.xml—<builtin-modules>and index<modules>sections trimmed; the 27 bundled modules and 5 bundled indexes no longer listed individually.Compatibility
Explicit
conf.xmlentries always take precedence over SPI discovery for the same namespace URI/indexid— no upgrade action required.enabled="no"suppresses a bundled module/index without removing its JAR.Related
#6563 tracks a gap this PR (and #6550) opens up: a module can now be active with no
conf.xmlentry at all, and suppressedenabled="no"entries are silently discarded — existingutil:/system:functions can't report "what's loaded and why" anymore. Follow-up work, not blocking this PR.Test plan
mvn validate -pl exist-distribution— trimmed canonicalconf.xmlstill validatesmvn test -pl exist-core— full unit suite passesenabled="no"on a module's conf.xml entry → not loaded despite SPI discovery🤖 Generated with Claude Code