Context
The ModuleFactory/IndexFactory SPI work replaced ad hoc Class.forName().getMethod().invoke() reflection (used by a broker pool service to wire the optional vector extension into core) with a small ServiceLoader-discovered interface. That review pass turned up two more instances of the same underlying shape elsewhere in the codebase: core needs optional behavior from an extension module without a hard compile-time dependency, and currently gets it via reflection that's more fragile than a proper SPI would be.
Shape 1: RESTServer → EXQuery RestXQ extension
exist-core/src/main/java/org/exist/http/RESTServer.java's constructor reflectively probes for org.exist.extensions.exquery.modules.request.RequestModule (reading its EXQ_REQUEST_ATTR constant via a reflective field access) and, if present, for org.exist.extensions.exquery.restxq.impl.adapters.HttpServletRequestAdapter, then uses MethodHandles.Lookup + LambdaMetafactory to reflectively build a BiFunction<HttpServletRequest, FilterInputStreamCacheConfiguration, HttpRequest> from a constructor handle — all just to avoid a compile-time dependency from exist-core on the RestXQ extension module. This is considerably more fragile/hard to follow than the pattern already established for modules and indexes.
Fix: a small ExQueryRequestAdapterFactory SPI (ServiceLoader-discovered, mirroring the vector extension's hook interface), implemented by HttpServletRequestAdapter in extensions/exquery/restxq and registered via META-INF/services.
Shape 2: Annotations.java → org.exist.xquery.xUnit.Annotations
exist-core/src/main/java/org/exist/xquery/Annotations.java has a static initializer that force-instantiates a hardcoded class name (org.exist.xquery.xUnit.Annotations) inside a bare catch (final Exception e) {} that silently swallows any exception — not just "class not found," but any genuine bug thrown from that class's own registration path too.
Needs investigation, not a direct port of shape 1's fix: org.exist.xquery.xUnit.Annotations does not exist anywhere in this repository (no xUnit module, no extension providing it). Either:
- it's dead/vestigial code left over from a removed extension, and should just be deleted, or
- it's an intentional extension point for an out-of-tree/third-party plugin, in which case the fix isn't "port it to the same SPI as shape 1" but "replace a reflective probe for one hardcoded class name with a real
ServiceLoader-based SPI that supports multiple providers and doesn't hide real errors behind a blanket catch."
Acceptance criteria
Context
The
ModuleFactory/IndexFactorySPI work replaced ad hocClass.forName().getMethod().invoke()reflection (used by a broker pool service to wire the optional vector extension into core) with a smallServiceLoader-discovered interface. That review pass turned up two more instances of the same underlying shape elsewhere in the codebase: core needs optional behavior from an extension module without a hard compile-time dependency, and currently gets it via reflection that's more fragile than a proper SPI would be.Shape 1:
RESTServer→ EXQuery RestXQ extensionexist-core/src/main/java/org/exist/http/RESTServer.java's constructor reflectively probes fororg.exist.extensions.exquery.modules.request.RequestModule(reading itsEXQ_REQUEST_ATTRconstant via a reflective field access) and, if present, fororg.exist.extensions.exquery.restxq.impl.adapters.HttpServletRequestAdapter, then usesMethodHandles.Lookup+LambdaMetafactoryto reflectively build aBiFunction<HttpServletRequest, FilterInputStreamCacheConfiguration, HttpRequest>from a constructor handle — all just to avoid a compile-time dependency fromexist-coreon the RestXQ extension module. This is considerably more fragile/hard to follow than the pattern already established for modules and indexes.Fix: a small
ExQueryRequestAdapterFactorySPI (ServiceLoader-discovered, mirroring the vector extension's hook interface), implemented byHttpServletRequestAdapterinextensions/exquery/restxqand registered viaMETA-INF/services.Shape 2:
Annotations.java→org.exist.xquery.xUnit.Annotationsexist-core/src/main/java/org/exist/xquery/Annotations.javahas a static initializer that force-instantiates a hardcoded class name (org.exist.xquery.xUnit.Annotations) inside a barecatch (final Exception e) {}that silently swallows any exception — not just "class not found," but any genuine bug thrown from that class's own registration path too.Needs investigation, not a direct port of shape 1's fix:
org.exist.xquery.xUnit.Annotationsdoes not exist anywhere in this repository (noxUnitmodule, no extension providing it). Either:ServiceLoader-based SPI that supports multiple providers and doesn't hide real errors behind a blanket catch."Acceptance criteria
RESTServer's RestXQ request-adapter wiring uses aServiceLoader-discovered SPI instead ofMethodHandles/LambdaMetafactoryreflection.Annotations.java's xUnit hook is either removed (if confirmed dead) or converted to a proper SPI with narrowed exception handling (if an active extension point is confirmed to still be wanted).