This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push:
new ec69fbf852 Avoid reflective InputSource modelId mutation (#12147)
ec69fbf852 is described below
commit ec69fbf852d39834349c0fc9836193802b8c2451
Author: Silva Dev BR <[email protected]>
AuthorDate: Sat Jun 13 19:09:40 2026 -0300
Avoid reflective InputSource modelId mutation (#12147)
* Avoid reflective modelId mutation in InputSource
* Track POM depth during modelId extraction
* Cover parent modelId dependency nesting
* Fix extractModelId thread safety, early exit, and static methods
- Remove InputFactoryHolder singleton: create XMLInputFactory per call
(consistent with DefaultXmlService and PomXmlRootDetector). The cached
factory set IS_COALESCING and IS_REPLACING_ENTITY_REFERENCES which are
unnecessary for GAV extraction, and sharing the factory is not
guaranteed thread-safe by the StAX spec.
- Fix early exit: only break when all three direct coordinates are found.
The previous condition broke early on parent fallbacks, missing direct
groupId/version that appear after artifactId in non-standard ordering.
- Make extractModelId(InputStream) and extractModelId(Reader) static.
- Add tests for direct coordinates overriding parent and null extraction.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Rename reader to xmlReader in extractModelId(InputStream)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Fix InputSource location to use path URI, not request location
MavenStaxReader passes InputSource.getLocation() as the StAX systemId
to StreamSource. The request location can be a GAV coordinate (e.g.
"org.test:parent-pom:1.0") for remote parents, which is not a valid
URL and causes MalformedURLException during entity resolution. Restore
the original behavior: use path.toUri().toString() or null.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Guillaume Nodet <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../apache/maven/impl/DefaultModelXmlFactory.java | 153 ++++++++++++++++++++-
.../maven/impl/model/DefaultModelBuilder.java | 15 --
.../maven/impl/DefaultModelXmlFactoryTest.java | 127 +++++++++++++++++
3 files changed, 277 insertions(+), 18 deletions(-)
diff --git
a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
index e7a699e3b0..e18e723d8d 100644
---
a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
+++
b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
@@ -18,6 +18,15 @@
*/
package org.apache.maven.impl;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
@@ -88,10 +97,31 @@ private Model doRead(XmlReaderRequest request) throws
XmlReaderException {
throw new IllegalArgumentException("path, url, reader or
inputStream must be non null");
}
try {
+ String modelId = request.getModelId();
+
+ if (modelId == null) {
+ if (inputStream != null) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ inputStream.transferTo(baos);
+ byte[] buf = baos.toByteArray();
+ modelId = extractModelId(new ByteArrayInputStream(buf));
+ inputStream = new ByteArrayInputStream(buf);
+ } else if (reader != null) {
+ CharArrayWriter caw = new CharArrayWriter();
+ reader.transferTo(caw);
+ char[] buf = caw.toCharArray();
+ modelId = extractModelId(new CharArrayReader(buf));
+ reader = new CharArrayReader(buf);
+ } else if (path != null) {
+ try (InputStream is = Files.newInputStream(path)) {
+ modelId = extractModelId(is);
+ }
+ }
+ }
+
InputSource source = null;
- if (request.getModelId() != null || request.getLocation() != null)
{
- source = new InputSource(
- request.getModelId(), path != null ?
path.toUri().toString() : null);
+ if (modelId != null || request.getLocation() != null) {
+ source = new InputSource(modelId, path != null ?
path.toUri().toString() : null);
}
MavenStaxReader xml = request.getTransformer() != null
? new MavenStaxReader(request.getTransformer()::transform)
@@ -115,6 +145,123 @@ private Model doRead(XmlReaderRequest request) throws
XmlReaderException {
}
}
+ private static String extractModelId(InputStream inputStream) {
+ try {
+ XMLStreamReader xmlReader =
XMLInputFactory.newFactory().createXMLStreamReader(inputStream);
+ try {
+ return extractModelId(xmlReader);
+ } finally {
+ xmlReader.close();
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static String extractModelId(Reader reader) {
+ try {
+ XMLStreamReader xmlReader =
XMLInputFactory.newFactory().createXMLStreamReader(reader);
+ try {
+ return extractModelId(xmlReader);
+ } finally {
+ xmlReader.close();
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static String extractModelId(XMLStreamReader reader) throws
XMLStreamException {
+ String groupId = null;
+ String artifactId = null;
+ String version = null;
+ String parentGroupId = null;
+ String parentVersion = null;
+
+ int depth = 0;
+ boolean inProject = false;
+ boolean inParent = false;
+ String currentElement = null;
+
+ while (reader.hasNext()) {
+ int event = reader.next();
+
+ if (event == XMLStreamConstants.START_ELEMENT) {
+ depth++;
+ String localName = reader.getLocalName();
+
+ if (depth == 1 && "project".equals(localName)) {
+ inProject = true;
+ } else if (inProject && depth == 2 &&
"parent".equals(localName)) {
+ inParent = true;
+ } else if (inProject
+ && (depth == 2 || (depth == 3 && inParent))
+ && ("groupId".equals(localName)
+ || "artifactId".equals(localName)
+ || "version".equals(localName))) {
+ currentElement = localName;
+ }
+ } else if (event == XMLStreamConstants.END_ELEMENT) {
+ String localName = reader.getLocalName();
+
+ if ("parent".equals(localName)) {
+ inParent = false;
+ } else if ("project".equals(localName)) {
+ break;
+ }
+ currentElement = null;
+ depth--;
+ } else if (event == XMLStreamConstants.CHARACTERS &&
currentElement != null) {
+ String text = reader.getText().trim();
+ if (!text.isEmpty()) {
+ if (inParent) {
+ switch (currentElement) {
+ case "groupId":
+ parentGroupId = text;
+ break;
+ case "version":
+ parentVersion = text;
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (currentElement) {
+ case "groupId":
+ groupId = text;
+ break;
+ case "artifactId":
+ artifactId = text;
+ break;
+ case "version":
+ version = text;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if (groupId != null && artifactId != null && version != null) {
+ break;
+ }
+ }
+
+ if (groupId == null) {
+ groupId = parentGroupId;
+ }
+ if (version == null) {
+ version = parentVersion;
+ }
+
+ if (groupId != null && artifactId != null && version != null) {
+ return groupId + ":" + artifactId + ":" + version;
+ }
+
+ return null;
+ }
+
@Override
public void write(XmlWriterRequest<Model> request) throws
XmlWriterException {
requireNonNull(request, "request");
diff --git
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index b7e2b51e44..ebbb61a227 100644
---
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -21,7 +21,6 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -68,7 +67,6 @@
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.Exclusion;
import org.apache.maven.api.model.InputLocation;
-import org.apache.maven.api.model.InputSource;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Parent;
import org.apache.maven.api.model.Profile;
@@ -1536,19 +1534,6 @@ Model doReadFileModel() throws ModelBuilderException {
"Malformed POM " + modelSource.getLocation() + ":
" + e.getMessage(),
e);
}
-
- InputLocation loc = model.getLocation("");
- InputSource v4src = loc != null ? loc.getSource() : null;
- if (v4src != null) {
- try {
- Field field =
InputSource.class.getDeclaredField("modelId");
- field.setAccessible(true);
- field.set(v4src, ModelProblemUtils.toId(model));
- } catch (Throwable t) {
- // TODO: use a lazy source ?
- throw new IllegalStateException("Unable to set modelId
on InputSource", t);
- }
- }
} catch (XmlReaderException e) {
add(
Severity.FATAL,
diff --git
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
index 8a8b4b21b0..dbdbfd454f 100644
---
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
+++
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
@@ -18,10 +18,13 @@
*/
package org.apache.maven.impl;
+import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
import java.util.function.Function;
+import org.apache.maven.api.model.InputSource;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.xml.XmlReaderException;
import org.apache.maven.api.services.xml.XmlReaderRequest;
@@ -31,6 +34,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -122,6 +127,128 @@ void testMalformedModelVersion() throws Exception {
assertEquals("invalid.version", model.getModelVersion());
}
+ @Test
+ void testExtractsModelIdIntoInputSource() throws Exception {
+ String xml = """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.example</groupId>
+ <artifactId>child</artifactId>
+ <version>1</version>
+ </project>""";
+
+ Model model = factory.read(XmlReaderRequest.builder()
+ .reader(new StringReader(xml))
+ .location("pom.xml")
+ .strict(true)
+ .build());
+
+ InputSource source = model.getLocation("").getSource();
+ assertNotNull(source);
+ assertEquals("org.example:child:1", source.getModelId());
+ }
+
+ @Test
+ void testExtractsModelIdUsingParentFallback() throws Exception {
+ String xml = """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.example</groupId>
+ <artifactId>parent</artifactId>
+ <version>1</version>
+ </parent>
+ <artifactId>child</artifactId>
+ </project>""";
+
+ Model model = factory.read(XmlReaderRequest.builder()
+ .reader(new StringReader(xml))
+ .location("pom.xml")
+ .strict(true)
+ .build());
+
+ InputSource source = model.getLocation("").getSource();
+ assertNotNull(source);
+ assertEquals("org.example:child:1", source.getModelId());
+ }
+
+ @Test
+ void testExtractsParentModelIdWithoutUsingDependencyCoordinates() throws
Exception {
+ String xml = """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.example</groupId>
+ <artifactId>parent</artifactId>
+ <version>1</version>
+ </parent>
+ <artifactId>child</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>dep.group</groupId>
+ <artifactId>dep-art</artifactId>
+ <version>2.0</version>
+ </dependency>
+ </dependencies>
+ </project>""";
+
+ Model model = factory.read(XmlReaderRequest.builder()
+ .inputStream(new
ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)))
+ .location("pom.xml")
+ .strict(true)
+ .build());
+
+ InputSource source = model.getLocation("").getSource();
+ assertNotNull(source);
+ assertEquals("org.example:child:1", source.getModelId());
+ }
+
+ @Test
+ void testExtractsDirectCoordinatesOverParent() throws Exception {
+ String xml = """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.parent</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0</version>
+ </parent>
+ <artifactId>child</artifactId>
+ <groupId>org.child</groupId>
+ <version>2.0</version>
+ </project>""";
+
+ Model model = factory.read(XmlReaderRequest.builder()
+ .reader(new StringReader(xml))
+ .location("pom.xml")
+ .strict(true)
+ .build());
+
+ InputSource source = model.getLocation("").getSource();
+ assertNotNull(source);
+ assertEquals("org.child:child:2.0", source.getModelId());
+ }
+
+ @Test
+ void testMissingArtifactIdProducesNullModelId() throws Exception {
+ String xml = """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.example</groupId>
+ <version>1</version>
+ </project>""";
+
+ Model model = factory.read(XmlReaderRequest.builder()
+ .reader(new StringReader(xml))
+ .location("pom.xml")
+ .strict(true)
+ .build());
+
+ InputSource source = model.getLocation("").getSource();
+ assertNotNull(source);
+ assertNull(source.getModelId());
+ }
+
@Test
void testWriteWithoutFormatterDisablesLocationTracking() throws Exception {
// minimal valid model we can round-trip