Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.maven.model.v4;

import javax.xml.stream.XMLStreamException;

import java.io.StringReader;
import java.util.Locale;

import org.apache.maven.api.model.InputSource;
import org.apache.maven.api.model.Model;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class MavenStaxReaderNamespaceTest {

private static final InputSource NO_INPUT_SOURCE = null;

private static final String POM_41 = ""
+ "<project xmlns=\"http://maven.apache.org/POM/4.1.0\" "
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
+ " <modelVersion>4.1.0</modelVersion>"
+ " <groupId>com.acme</groupId>"
+ " <artifactId>demo</artifactId>"
+ " <version>1.0</version>"
+ "</project>";

private static final String POM_40 = ""
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" "
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
+ " <modelVersion>4.0.0</modelVersion>"
+ " <groupId>com.acme</groupId>"
+ " <artifactId>demo</artifactId>"
+ " <version>1.0</version>"
+ "</project>";

private static final String POM_NO_NS = ""
+ "<project>"
+ " <modelVersion>4.0.0</modelVersion>"
+ " <groupId>com.acme</groupId>"
+ " <artifactId>demo</artifactId>"
+ " <version>1.0</version>"
+ "</project>";

private static final String POM_BAD_NS = ""
+ "<project xmlns=\"http://example.com/not/pom\">"
+ " <modelVersion>4.1.0</modelVersion>"
+ " <groupId>com.acme</groupId>"
+ " <artifactId>demo</artifactId>"
+ " <version>1.0</version>"
+ "</project>";

@Test
void acceptsPom41() throws Exception {
MavenStaxReader reader = new MavenStaxReader();
Model model = reader.read(new StringReader(POM_41), /*strict*/ true, NO_INPUT_SOURCE);
assertNotNull(model);
assertEquals("com.acme", model.getGroupId());
assertEquals("demo", model.getArtifactId());
assertEquals("1.0", model.getVersion());
}

@Test
void acceptsPom40() throws Exception {
MavenStaxReader reader = new MavenStaxReader();
Model model = reader.read(new StringReader(POM_40), true, NO_INPUT_SOURCE);
assertNotNull(model);
assertEquals("com.acme", model.getGroupId());
assertEquals("demo", model.getArtifactId());
assertEquals("1.0", model.getVersion());
}

@Test
void acceptsPomWithoutNamespace() throws Exception {
MavenStaxReader reader = new MavenStaxReader();
Model model = reader.read(new StringReader(POM_NO_NS), true, NO_INPUT_SOURCE);
assertNotNull(model);
assertEquals("com.acme", model.getGroupId());
assertEquals("demo", model.getArtifactId());
assertEquals("1.0", model.getVersion());
}

@Test
void rejectsUnexpectedPomNamespace() {
MavenStaxReader reader = new MavenStaxReader();
XMLStreamException ex = assertThrows(
XMLStreamException.class, () -> reader.read(new StringReader(POM_BAD_NS), true, NO_INPUT_SOURCE));
// sanity check: message mentions unrecognized namespace
assertTrue(ex.getMessage().toLowerCase(Locale.ROOT).contains("unrecognized pom namespace"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.maven.model.v4;

import javax.xml.stream.XMLStreamException;

import java.io.StringReader;

import org.apache.maven.api.metadata.Metadata;
import org.apache.maven.metadata.v4.MetadataStaxReader;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class MetadataStaxReaderNamespaceTest {

private static final String META_110 = ""
+ "<metadata xmlns=\"http://maven.apache.org/METADATA/1.1.0\">"
+ " <groupId>com.acme</groupId>"
+ " <artifactId>demo</artifactId>"
+ " <versioning>"
+ " <latest>1.0</latest>"
+ " <release>1.0</release>"
+ " </versioning>"
+ "</metadata>";

private static final String META_NO_NS = ""
+ "<metadata>"
+ " <groupId>com.acme</groupId>"
+ " <artifactId>demo</artifactId>"
+ " <versioning>"
+ " <latest>1.0</latest>"
+ " </versioning>"
+ "</metadata>";

private static final String META_BAD_NS = ""
+ "<metadata xmlns=\"http://example.com/not/metadata\">"
+ " <groupId>com.acme</groupId>"
+ " <artifactId>demo</artifactId>"
+ "</metadata>";

@Test
void acceptsMetadata110() throws Exception {
MetadataStaxReader reader = new MetadataStaxReader();
Metadata metadata = reader.read(new StringReader(META_110), /*strict*/ true);
assertNotNull(metadata);
assertEquals("com.acme", metadata.getGroupId());
assertEquals("demo", metadata.getArtifactId());
assertNotNull(metadata.getVersioning());
assertEquals("1.0", metadata.getVersioning().getLatest());
}

@Test
void acceptsMetadataWithoutNamespace() throws Exception {
MetadataStaxReader reader = new MetadataStaxReader();
Metadata metadata = reader.read(new StringReader(META_NO_NS), true);
assertNotNull(metadata);
assertEquals("com.acme", metadata.getGroupId());
assertEquals("demo", metadata.getArtifactId());
}

@Test
void rejectsUnexpectedMetadataNamespace() {
MetadataStaxReader reader = new MetadataStaxReader();
XMLStreamException ex =
assertThrows(XMLStreamException.class, () -> reader.read(new StringReader(META_BAD_NS), true));
assertTrue(ex.getMessage().toLowerCase().contains("unrecognized metadata namespace"));
}
}
76 changes: 51 additions & 25 deletions src/mdo/reader-stax.vm
Original file line number Diff line number Diff line change
Expand Up @@ -263,32 +263,58 @@ public class ${className} {
#if ( $needXmlContext )
Deque<Object> context = new ArrayDeque<>();
#end
$rootUcapName $rootLcapName = null;
int eventType = parser.getEventType();
boolean parsed = false;
while (eventType != XMLStreamReader.END_DOCUMENT) {
if (eventType == XMLStreamReader.START_ELEMENT) {
if (strict && ! "${rootTag}".equals(parser.getLocalName())) {
throw new XMLStreamException("Expected root element '${rootTag}' but found '" + parser.getName() + "'", parser.getLocation(), null);
} else if (parsed) {
// fallback, already expected a XMLStreamException due to invalid XML
throw new XMLStreamException("Duplicated tag: '${rootTag}'", parser.getLocation(), null);
}
#if ( $locationTracking )
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), inputSrc);
#elseif ( $needXmlContext )
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), context);
#else
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI());
#end
parsed = true;
$rootUcapName $rootLcapName = null;
int eventType = parser.getEventType();
boolean parsed = false;
while (eventType != XMLStreamReader.END_DOCUMENT) {
if (eventType == XMLStreamReader.START_ELEMENT) {
if (strict && ! "${rootTag}".equals(parser.getLocalName())) {
throw new XMLStreamException("Expected root element '${rootTag}' but found '" + parser.getName() + "'", parser.getLocation(), null);
} else if (parsed) {
throw new XMLStreamException("Duplicated tag: '${rootTag}'", parser.getLocation(), null);
}
// Root namespace policy:
// - POM ('project'): allow no namespace or any http://maven.apache.org/POM/<version>
// - METADATA ('metadata'): allow no namespace or any http://maven.apache.org/METADATA/<version>
// - Other readers: do not enforce root namespace here (child checks still apply when strict)
if (strict) {
final String rootNs = parser.getNamespaceURI();
final boolean hasNs = rootNs != null && !rootNs.isEmpty();

if ("project".equals("${rootTag}")) {
if (hasNs && !rootNs.startsWith("http://maven.apache.org/POM/")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. We have generators to generate specific readers, not a generic one which covers all use cases. So it's wrong to put hardcoded values for one or two models in the output of all readers.
The way to go is to retrieve the namespace using modello. The helper needs to be enhanced:
https://github.com/codehaus-plexus/modello/blob/ca30aba14346bf0e183efd2c442f061024c40111/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/Helper.java
It needs a new method xmlModelMetadata which would return the XmlModelMetadata from which we can get the target namespace.
Once modello is fixed, we can update the velocity templates to leverage that using Helper.xmlModelMetadata(model).namespace.

throw new XMLStreamException("Unrecognized POM namespace '" + rootNs + "'. Expected something like 'http://maven.apache.org/POM/4.x.y' or no namespace.",
parser.getLocation(), null);
}
eventType = parser.next();
}
if (parsed) {
return $rootLcapName;
}
throw new XMLStreamException("Expected root element '${rootTag}' but found no element at all: invalid XML document", parser.getLocation(), null);
} else if ("metadata".equals("${rootTag}")) {
if (hasNs && !rootNs.startsWith("http://maven.apache.org/METADATA/")) {
throw new XMLStreamException("Unrecognized METADATA namespace '" + rootNs + "'. Expected something like 'http://maven.apache.org/METADATA/1.x.y' or no namespace.",
parser.getLocation(), null);
}
} else {
// Other generated models (settings, lifecycles, extensions, plugin, toolchains, …):
// no hard-coded enforcement at the root level.
}
}

#if ( $locationTracking )
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), inputSrc);
#elseif ( $needXmlContext )
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), context);
#else
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI());
#end
parsed = true;
}
eventType = parser.next();
}

if (parsed) {
return $rootLcapName;
}
throw new XMLStreamException(
"Expected root element '${rootTag}' but found no element at all: invalid XML document",
parser.getLocation(), null);
} //-- ${root.name} read(XMLStreamReader, boolean)

#foreach ( $class in $model.allClasses )
Expand Down
Loading