Repository navigation
fix: decouple ModelProcessor from XML-specific types (#13707) - #13722
charangowdamd-cmd wants to merge 1 commit into
Conversation
gnodet-bot
left a comment
There was a problem hiding this comment.
This PR makes good progress on decoupling ModelProcessor from XML-specific types, but it introduces several correctness and design issues that need to be addressed.
Critical: InterningTransformer regression
The new DefaultModelProcessor.doRead() uses new DefaultModelBuilder.InterningTransformer() (no-arg constructor), which always uses DEFAULT_CONTEXTS. The old code used new InterningTransformer(session), which reads MAVEN_MODEL_BUILDER_INTERNS from user/system properties to let users customize which XML contexts to intern. This silently drops a user-facing configuration knob without replacement.
Critical: modelId lost in the toSource() fast path
In the SPI bridge ModelProcessor.toSource(), the fast path returns Sources.fromPath(request.getPath()), which creates a plain PathSource (not ModelSource). PathSource has no getModelId(). Downstream, DefaultModelProcessor.doRead() only sets modelId when source instanceof ModelSource — so callers that set request.modelId(...) silently get it dropped on the fast path.
Design: XML-specific code in the SPI interface
The ModelProcessor SPI now contains ~80 lines of implementation in the deprecated read(XmlReaderRequest) default method, including toSource() with ByteArrayInputStream, XmlReaderRequest, XmlReaderException, etc. SPI interfaces should not contain implementation logic or import XML-specific types. This code belongs in an abstract class or a utility in the impl module.
Design: InterningTransformer accessed via inner class cross-reference
DefaultModelProcessor references DefaultModelBuilder.InterningTransformer — a package-private inner class of an unrelated class. This creates tight coupling that will break if either class moves. InterningTransformer should be extracted to a dedicated top-level class in the impl package.
Robustness: error handling via cause-chain introspection
The deprecated read(XmlReaderRequest) bridge re-throws by walking e.getCause() chains to find XmlReaderException or IOException. This is fragile: any wrapping by an intermediate layer will hide the original exception, silently converting it to the fallback XmlReaderException(e.getMessage(), null, e) — losing location information. The old separate catch (XmlReaderException) / catch (IOException) blocks were explicit and predictable.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| .location(source.getLocation()) | ||
| .path(source.getPath()) | ||
| .inputStream(is) | ||
| .transformer(new DefaultModelBuilder.InterningTransformer()); |
There was a problem hiding this comment.
InterningTransformer() (no-arg) always uses DEFAULT_CONTEXTS. The old code passed the session so users could configure which contexts to intern via the maven.model.builder.interns property (MAVEN_MODEL_BUILDER_INTERNS). That customization is now silently lost.
This needs either:
- The
Sessionto be available here (requires threading it through), or - Passing
optionsthat include the session-derived contexts, or - At minimum, a
@deprecatednote on the configuration knob explaining it no longer works.
| && request.getInputStream() == null | ||
| && request.getReader() == null | ||
| && request.getURL() == null) { | ||
| return Sources.fromPath(request.getPath()); |
There was a problem hiding this comment.
Sources.fromPath() returns a PathSource, not a ModelSource. Callers that set request.modelId(...) will silently lose the modelId: DefaultModelProcessor.doRead() only propagates modelId when source instanceof ModelSource, which PathSource is not.
Use Sources.resolvedSource(request.getPath(), request.getModelId()) instead, or check request.getModelId() != null before taking this fast path.
| * @since 4.0.0 | ||
| */ | ||
| @Nonnull | ||
| Model read(@Nonnull Source source, @Nullable Map<String, ?> options) throws ModelParserException; |
There was a problem hiding this comment.
ByteArrayInputStream, XmlReaderException, XmlReaderRequest, ModelParser, ModelParserException, Sources, ModelSource, etc., and contains ~80 lines of XML-specific implementation. SPI interfaces should be minimal contracts with no implementation logic.
The deprecated bridge method and toSource() should live in an abstract base class (e.g. AbstractModelProcessor) or a package-private utility class in maven-impl, not in the SPI.
| .location(source.getLocation()) | ||
| .path(source.getPath()) | ||
| .inputStream(is) | ||
| .transformer(new DefaultModelBuilder.InterningTransformer()); |
There was a problem hiding this comment.
💡 Design: DefaultModelBuilder.InterningTransformer is a package-private inner class of DefaultModelBuilder. Cross-referencing it from DefaultModelProcessor creates tight coupling: moving either class would break compilation. Extract InterningTransformer to a dedicated package-private top-level class (e.g. ModelInterningTransformer) in org.apache.maven.impl.model.
aa9f466 to
99e6c94
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
This PR makes good progress on the refactoring goals, addressing most issues from the previous review. Three issues remain.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
99e6c94 to
1ec98a4
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
Following up on my earlier review — the first two issues (transformer drop and SPI Javadoc) are now fully addressed. One new design concern was introduced in this iteration.
Previous findings:
- ✅ Deprecated bridge drops transformer — fixed by passing transformer in options map
- ✅ SPI Javadoc references implementation classes — Javadoc correctly updated to abstract description
- ✅
InterningTransformerextracted toModelInterningTransformertop-level class - ✅
UrlModelSourcerecord extracted from the anonymous inline implementation
New issue: DefaultSuperPomProvider now imports DefaultModelProcessor (impl→impl coupling)
See inline comment below.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| import org.apache.maven.api.services.xml.XmlReaderRequest; | ||
| import org.apache.maven.api.spi.ModelParser; | ||
| import org.apache.maven.api.spi.ModelParserException; | ||
| import org.apache.maven.impl.model.DefaultModelProcessor; |
There was a problem hiding this comment.
DefaultModelProcessor.urlModelSource()
DefaultSuperPomProvider previously interacted with ModelProcessor exclusively through the SPI interface. This commit introduces a direct import of DefaultModelProcessor (a concrete implementation class) solely to call DefaultModelProcessor.urlModelSource(url, modelId).
This creates tight coupling between two implementation classes that should remain independent. If DefaultModelProcessor is refactored, renamed, or the urlModelSource factory is moved, DefaultSuperPomProvider breaks silently.
The fix is to move urlModelSource() — or an equivalent ModelSource factory for URL + modelId — to the existing Sources utility class in maven-api-core. Sources already provides fromPath(), buildSource(), and resolvedSource(); a URL variant fits naturally there and would eliminate this coupling:
// In Sources.java (maven-api-core)
@Nonnull
public static ModelSource urlSource(@Nonnull URL url, @Nullable String modelId) {
return new UrlModelSource(url, modelId);
}DefaultSuperPomProvider could then use Sources.urlSource(url, modelId) without importing any impl class. The UrlModelSource record itself can live in Sources (package-private) or be made package-private in the same module.
Summary
Decouple
ModelProcessorfrom XML-specific types by introducing a format-agnosticread(Source, Map)method.Fixes #13707
Motivation
ModelProcessoris intended to provide a format-independent entry point for reading Maven models, delegating parsing to registeredModelParserSPI implementations before falling back to XML parsing.Previously, its API exposed
XmlReaderRequestand XML-specific exceptions, requiring callers to construct XML-specific requests even when working with genericSourceinstances. Parser failures were also wrapped inIOException, making exception handling inconsistent with theModelParserAPI.This change aligns
ModelProcessorwith theModelParserAPI and prepares it for the model dialect system (#13613).Changes Made
Updated
ModelProcessorAPIread(Source, Map)as a format-agnostic entry point.read(XmlReaderRequest)while retaining backward compatibility through delegation to the new method.Updated
DefaultModelProcessorModelParserSPI implementations.XmlReaderRequestinternally.ModelParserException, preserving line and column information where available.ModelParserExceptionwith suppressed exceptions.Updated model processing callers
DefaultSuperPomProviderandDefaultModelBuilderto passSourceinstances and parser options directly.InterningTransformersetup fromDefaultModelBuilder.Updated tests
DefaultModelProcessorTestto test the new API.read(XmlReaderRequest)method.Verification
mvn spotless:applymvn checkstyle:check— no violationsapi/maven-api-spiandimpl/maven-implCompatibility
The existing
read(XmlReaderRequest)method remains available as a deprecated method, allowing existing callers to transition to the new API.License