[MNG-7876] Add model version analysis and downgrade (#1235)

This commit is contained in:
Guillaume Nodet 2023-09-11 18:17:26 +02:00 committed by GitHub
parent 46fd5e9586
commit 45075233c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 707 additions and 26 deletions

View File

@ -214,7 +214,7 @@
</field> </field>
<field xml.attribute="true" xml.tagName="root"> <field xml.attribute="true" xml.tagName="root">
<name>root</name> <name>root</name>
<version>4.0.0+</version> <version>4.1.0+</version>
<description> <description>
<![CDATA[ <![CDATA[
Indicates that this project is the root project, located in the upper directory of the source tree. Indicates that this project is the root project, located in the upper directory of the source tree.
@ -225,6 +225,19 @@
<type>boolean</type> <type>boolean</type>
<defaultValue>false</defaultValue> <defaultValue>false</defaultValue>
</field> </field>
<field xml.attribute="true" xml.tagName="preserve.model.version">
<name>preserveModelVersion</name>
<version>4.1.0+</version>
<description>
<![CDATA[
Indicates if the build POM for this project should be preserved or downgraded to the lowest
compatible version.
<br><b>Since</b>: Maven 4.0.0
]]>
</description>
<type>boolean</type>
<defaultValue>false</defaultValue>
</field>
<field> <field>
<name>inceptionYear</name> <name>inceptionYear</name>
<version>3.0.0+</version> <version>3.0.0+</version>

View File

@ -18,7 +18,7 @@ under the License.
--> -->
<project> <project>
<modelVersion>4.0.1</modelVersion> <modelVersion>4.9.1</modelVersion>
<groupId>tests.project</groupId> <groupId>tests.project</groupId>
<artifactId>future-model-version</artifactId> <artifactId>future-model-version</artifactId>
<version>1</version> <version>1</version>

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<source> 1.5 </source>
<target xml:space="preserve"> 1.5 </target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>test</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>default-active</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</profile>
<profile>
<id>file</id>
<activation>
<file>
<exists>simple.xml</exists>
</file>
</activation>
<properties>
<profile.file>activated</profile.file>
</properties>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 https://maven.apache.org/xsd/maven-4.1.0.xsd"
root="true">
<modelVersion>4.1.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>lib</module> <!-- the library -->
<module>app</module> <!-- the application -->
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<source> 1.5 </source>
<target xml:space="preserve"> 1.5 </target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>test</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>default-active</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</profile>
<profile>
<id>file</id>
<activation>
<file>
<exists>simple.xml</exists>
</file>
</activation>
<properties>
<profile.file>activated</profile.file>
</properties>
</profile>
</profiles>
</project>

View File

@ -59,6 +59,7 @@ import org.apache.maven.model.building.ModelProblem.Version;
import org.apache.maven.model.building.ModelProblemCollector; import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.building.ModelProblemCollectorRequest; import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.interpolation.ModelVersionProcessor; import org.apache.maven.model.interpolation.ModelVersionProcessor;
import org.apache.maven.model.v4.MavenModelVersion;
import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.StringUtils;
/** /**
@ -143,15 +144,11 @@ public class DefaultModelValidator implements ModelValidator {
Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0); Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
// [MNG-6074] Maven should produce an error if no model version has been set in a POM file used to build an // The file pom may not contain the modelVersion yet, as it may be set later by the
// effective model. // ModelVersionXMLFilter.
// if (m.getModelVersion() != null && !m.getModelVersion().isEmpty()) {
// As of 3.4, the model version is mandatory even in raw models. The XML element still is optional in the validateModelVersion(problems, m.getModelVersion(), m, "4.0.0", "4.1.0");
// XML schema and this will not change anytime soon. We do not want to build effective models based on }
// models without a version starting with 3.4.
validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.V20, m.getModelVersion(), m);
validateModelVersion(problems, m.getModelVersion(), m, "4.0.0");
validateStringNoExpression("groupId", problems, Severity.WARNING, Version.V20, m.getGroupId(), m); validateStringNoExpression("groupId", problems, Severity.WARNING, Version.V20, m.getGroupId(), m);
if (parent == null) { if (parent == null) {
@ -257,6 +254,28 @@ public class DefaultModelValidator implements ModelValidator {
public void validateRawModel(Model ma, ModelBuildingRequest request, ModelProblemCollector problems) { public void validateRawModel(Model ma, ModelBuildingRequest request, ModelProblemCollector problems) {
org.apache.maven.api.model.Model m = ma.getDelegate(); org.apache.maven.api.model.Model m = ma.getDelegate();
// [MNG-6074] Maven should produce an error if no model version has been set in a POM file used to build an
// effective model.
//
// As of 3.4, the model version is mandatory even in raw models. The XML element still is optional in the
// XML schema and this will not change anytime soon. We do not want to build effective models based on
// models without a version starting with 3.4.
validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.V20, m.getModelVersion(), m);
validateModelVersion(problems, m.getModelVersion(), m, "4.0.0", "4.1.0");
String minVersion = new MavenModelVersion().getModelVersion(m);
if (m.getModelVersion() != null && compareModelVersions(minVersion, m.getModelVersion()) > 0) {
addViolation(
problems,
Severity.FATAL,
Version.V40,
"model",
null,
"the model contains elements that require a model version of " + minVersion,
m);
}
Parent parent = m.getParent(); Parent parent = m.getParent();
if (parent != null) { if (parent != null) {

View File

@ -61,13 +61,19 @@ public class SimpleProblemCollector implements ModelProblemCollector {
public void add(ModelProblemCollectorRequest req) { public void add(ModelProblemCollectorRequest req) {
switch (req.getSeverity()) { switch (req.getSeverity()) {
case FATAL: case FATAL:
if (!fatals.contains(req.getMessage())) {
fatals.add(req.getMessage()); fatals.add(req.getMessage());
}
break; break;
case ERROR: case ERROR:
if (!errors.contains(req.getMessage())) {
errors.add(req.getMessage()); errors.add(req.getMessage());
}
break; break;
case WARNING: case WARNING:
if (!warnings.contains(req.getMessage())) {
warnings.add(req.getMessage()); warnings.add(req.getMessage());
}
break; break;
} }
} }

View File

@ -28,6 +28,10 @@ under the License.
<name>Maven Model XML Transform</name> <name>Maven Model XML Transform</name>
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-xml</artifactId> <artifactId>plexus-xml</artifactId>

View File

@ -0,0 +1,113 @@
/*
* 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.transform;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.apache.maven.api.model.Model;
import org.apache.maven.model.transform.stax.BufferingParser;
import org.apache.maven.model.v4.MavenModelVersion;
import org.apache.maven.model.v4.MavenStaxReader;
public class ModelVersionDowngradeXMLFilter extends BufferingParser {
public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
private final List<Event> buffer = new ArrayList<>();
public ModelVersionDowngradeXMLFilter(XMLStreamReader delegate) {
super(delegate);
}
@Override
protected boolean accept() throws XMLStreamException {
Event e = bufferEvent();
buffer.add(e);
if (e.event == XMLStreamReader.END_DOCUMENT) {
ReplayParser p = new ReplayParser(this);
buffer.forEach(p::pushEvent);
p.next();
String version;
Model model = new MavenStaxReader().read(p, false, null);
if (model.isPreserveModelVersion()) {
version = model.getModelVersion();
} else {
model = model.withPreserveModelVersion(false);
version = new MavenModelVersion().getModelVersion(model);
}
int depth = 0;
boolean isModelVersion = false;
for (Event event : buffer) {
event.namespace = NAMESPACE_PREFIX + version;
// rewrite namespace
if (event.namespaces != null) {
for (int i = 0; i < event.namespaces.length; i++) {
if (event.namespaces[i].uri.startsWith(NAMESPACE_PREFIX)) {
event.namespaces[i].uri = event.namespace;
}
}
}
// rewrite xsi:schemaLocation attribute
if (event.attributes != null) {
for (Attribute attribute : event.attributes) {
if (attribute.namespace.equals("http://www.w3.org/2001/XMLSchema-instance")
&& attribute.name.equals("schemaLocation")) {
attribute.value = attribute
.value
.replaceAll(
"\\Q" + NAMESPACE_PREFIX + "\\E[0-9]\\.[0-9]\\.[0-9]",
NAMESPACE_PREFIX + version)
.replaceAll(
"http(s?)://maven\\.apache\\.org/xsd/maven-[0-9]\\.[0-9]\\.[0-9]\\.xsd",
"https://maven.apache.org/xsd/maven-" + version + ".xsd");
}
}
}
// Rewrite modelVersion
if (event.event == XMLStreamReader.START_ELEMENT) {
depth++;
isModelVersion = depth == 2 && event.name.equals("modelVersion");
}
if (event.event == XMLStreamReader.CHARACTERS && isModelVersion) {
event.text = version;
}
if (event.event == XMLStreamReader.END_ELEMENT) {
depth--;
isModelVersion = false;
}
pushEvent(event);
}
}
return false;
}
static class ReplayParser extends BufferingParser {
ReplayParser(XMLStreamReader delegate) {
super(delegate);
}
public void pushEvent(Event e) {
super.pushEvent(e);
}
}
}

View File

@ -27,9 +27,10 @@ import org.apache.maven.model.transform.stax.NodeBufferingParser;
public class ModelVersionXMLFilter extends NodeBufferingParser { public class ModelVersionXMLFilter extends NodeBufferingParser {
private static final Pattern S_FILTER = Pattern.compile("\\s+");
public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/"; public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
private static final Pattern S_FILTER = Pattern.compile("\\s+");
public ModelVersionXMLFilter(XMLStreamReader delegate) { public ModelVersionXMLFilter(XMLStreamReader delegate) {
super(delegate, "project"); super(delegate, "project");
} }

View File

@ -44,6 +44,8 @@ public class RawToConsumerPomXMLFilterFactory {
parser = new ModulesXMLFilter(parser); parser = new ModulesXMLFilter(parser);
// Adjust relativePath // Adjust relativePath
parser = new RelativePathXMLFilter(parser); parser = new RelativePathXMLFilter(parser);
// Downgrade modelVersion if needed
parser = new ModelVersionDowngradeXMLFilter(parser);
return parser; return parser;
} }

View File

@ -120,7 +120,7 @@ class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests {
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" + "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n" + " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n"
+ " http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" + " https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ " <modelVersion>4.0.0</modelVersion>\n" + " <modelVersion>4.0.0</modelVersion>\n"
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n" + " <groupId>org.sonatype.mavenbook.multispring</groupId>\n"
+ " <artifactId>parent</artifactId>\n" + " <artifactId>parent</artifactId>\n"
@ -163,7 +163,7 @@ class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests {
+ "\n" + "\n"
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" + "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" + " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ " <modelVersion>4.0.0</modelVersion>\n" + " <modelVersion>4.0.0</modelVersion>\n"
+ " <parent>\n" + " <parent>\n"
+ " <groupId>org.apache.maven</groupId>\n" + " <groupId>org.apache.maven</groupId>\n"
@ -226,4 +226,82 @@ class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests {
String actual = transform(input); String actual = transform(input);
assertThat(actual).and(expected).areIdentical(); assertThat(actual).and(expected).areIdentical();
} }
@Test
void downgradeModelVersion() throws Exception {
String input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<project xmlns=\"http://maven.apache.org/POM/4.1.0\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.1.0\n"
+ " http://maven.apache.org/xsd/maven-4.1.0.xsd\">\n"
+ " <modelVersion>4.1.0</modelVersion>\n"
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n"
+ " <artifactId>parent</artifactId>\n"
+ " <version>0.9-SNAPSHOT</version>\n"
+ " <packaging>pom</packaging>\n"
+ "</project>";
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n"
+ " https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ " <modelVersion>4.0.0</modelVersion>\n"
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n"
+ " <artifactId>parent</artifactId>\n"
+ " <version>0.9-SNAPSHOT</version>\n"
+ " <packaging>pom</packaging>\n"
+ "</project>";
String actual = transform(input);
assertThat(actual).and(expected).ignoreWhitespace().areIdentical();
}
@Test
void downgradeNotModelVersion() throws Exception {
String input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<project xmlns=\"http://maven.apache.org/POM/4.1.0\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.1.0 http://maven.apache.org/xsd/maven-4.1.0.xsd\""
+ " preserve.model.version=\"true\">\n"
+ " <modelVersion>4.1.0</modelVersion>\n"
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n"
+ " <artifactId>parent</artifactId>\n"
+ " <version>0.9-SNAPSHOT</version>\n"
+ " <packaging>pom</packaging>\n"
+ " <build>"
+ " <plugins>\n"
+ " <plugin>\n"
+ " <executions>\n"
+ " <execution>\n"
+ " <priority>1</priority>\n"
+ " </execution>\n"
+ " </executions>\n"
+ " </plugin>\n"
+ " </plugins>\n"
+ " </build>\n"
+ "</project>";
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<project xmlns=\"http://maven.apache.org/POM/4.1.0\"\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " preserve.model.version=\"true\""
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.1.0 https://maven.apache.org/xsd/maven-4.1.0.xsd\">\n"
+ " <modelVersion>4.1.0</modelVersion>\n"
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n"
+ " <artifactId>parent</artifactId>\n"
+ " <version>0.9-SNAPSHOT</version>\n"
+ " <packaging>pom</packaging>\n"
+ " <build>"
+ " <plugins>\n"
+ " <plugin>\n"
+ " <executions>\n"
+ " <execution>\n"
+ " <priority>1</priority>\n"
+ " </execution>\n"
+ " </executions>\n"
+ " </plugin>\n"
+ " </plugins>\n"
+ " </build>\n"
+ "</project>";
String actual = transform(input);
assertThat(actual).and(expected).ignoreWhitespace().areIdentical();
}
} }

View File

@ -82,6 +82,7 @@ under the License.
<param>packageModelV4=org.apache.maven.api.model</param> <param>packageModelV4=org.apache.maven.api.model</param>
<param>packageToolV4=org.apache.maven.model.v4</param> <param>packageToolV4=org.apache.maven.model.v4</param>
<param>isMavenModel=true</param> <param>isMavenModel=true</param>
<param>minimalVersion=4.0.0</param>
</params> </params>
</configuration> </configuration>
<executions> <executions>
@ -123,6 +124,7 @@ under the License.
<template>writer-ex.vm</template> <template>writer-ex.vm</template>
<template>reader-stax.vm</template> <template>reader-stax.vm</template>
<template>writer-stax.vm</template> <template>writer-stax.vm</template>
<template>model-version.vm</template>
</templates> </templates>
</configuration> </configuration>
</execution> </execution>

View File

@ -0,0 +1,62 @@
/*
* 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 java.io.InputStream;
import org.apache.maven.api.model.Model;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class MavenModelVersionTest {
private static Model model;
@BeforeAll
static void setup() throws Exception {
try (InputStream is = MavenModelVersionTest.class.getResourceAsStream("/xml/pom.xml")) {
model = new MavenStaxReader().read(is);
}
}
@Test
void testV4Model() {
assertEquals("4.0.0", new MavenModelVersion().getModelVersion(model));
}
@Test
void testV4ModelVersion() {
Model m = model.withModelVersion("4.1.0");
assertEquals("4.0.0", new MavenModelVersion().getModelVersion(m));
}
@Test
void testV4ModelRoot() {
Model m = model.withRoot(true);
assertEquals("4.1.0", new MavenModelVersion().getModelVersion(m));
}
@Test
void testV4ModelPreserveModelVersion() {
Model m = model.withPreserveModelVersion(true);
assertEquals("4.1.0", new MavenModelVersion().getModelVersion(m));
}
}

180
src/mdo/model-version.vm Normal file
View File

@ -0,0 +1,180 @@
#*
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.
*#
#parse ( "common.vm" )
#
#set ( $package = "${packageToolV4}" )
#set ( $className = "${model.name}ModelVersion" )
#
#set ( $root = $model.getClass( $model.getRoot($version), $version ) )
#
#MODELLO-VELOCITY#SAVE-OUTPUT-TO ${package.replace('.','/')}/${className}.java
// =================== DO NOT EDIT THIS FILE ====================
// Generated by Modello Velocity from ${template}
// template, any modifications will be overwritten.
// ==============================================================
package ${package};
import java.io.ObjectStreamException;
import java.nio.file.Path;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.maven.api.annotations.Generated;
import org.apache.maven.api.xml.XmlNode;
#foreach ( $class in $model.allClasses )
import ${packageModelV4}.${class.Name};
#end
@Generated
public class ${className} {
public String getModelVersion(${root.name} model) {
Objects.requireNonNull(model, "model cannot be null");
#set ( $String = $model.getClass().forName("java.lang.String") )
#set ( $Comparator = $model.getClass().forName("java.util.Comparator") )
#set ( $LinkedHashSet = $model.getClass().forName("java.util.LinkedHashSet") )
#set ( $HashMap = $model.getClass().forName("java.util.HashMap") )
#set ( $TreeSet = $model.getClass().forName("java.util.TreeSet") )
#set ( $Version = $model.getClass().forName("org.codehaus.modello.model.Version") )
#set ( $versions = $TreeSet.getConstructor( $Comparator ).newInstance( $Comparator.reverseOrder() ) )
#foreach ( $class in $model.allClasses )
#set ( $dummy = $versions.add( $class.versionRange.fromVersion ) )
#foreach ( $field in $class.allFields )
#if ( ! $Helper.xmlFieldMetadata( $field ).transient )
#set ( $dummy = $versions.add( $field.versionRange.fromVersion ) )
#end
#end
#end
#if ( $minimalVersion )
#set ( $minimal = $Version.getConstructor( $String ).newInstance( $minimalVersion ) )
#set ( $versions = $versions.headSet( $minimal, false ) )
#else
#set ( $dummy = $versions.remove( $Version.getConstructor( $String ).newInstance( "0.0.0" ) ) )
#end
#set ( $dummy = $versions.remove( $Version.getConstructor( $String ).newInstance( "32767.32767.32767" ) ) )
#foreach ( $version in $versions )
#set ( $v = $version.toString().replace('.', '_') )
// ${version}
if (is_${v}(model)) {
return "${version}";
}
#end
#if ( $minimalVersion )
return "$minimalVersion";
#else
return null;
#end
}
#foreach ( $version in $versions )
#set ( $v = $version.toString().replace('.', '_') )
#set ( $classesToCheck = $TreeSet.newInstance() )
#set ( $classToFields = $HashMap.newInstance() )
#foreach($unused in [1..10])
#foreach ( $class in $model.allClasses )
#foreach ( $field in $class.allFields )
#if ( ! $Helper.xmlFieldMetadata( $field ).transient )
#set ( $newInVersion = $field.versionRange.fromVersion.equals($version) )
#set ( $isAsso = false )
#if ( $field.toClass )
#set ( $ancestors = $Helper.ancestors( $field.toClass ) )
#foreach ( $cl in $ancestors )
#if ( $classToFields.containsKey( $cl ) )
#set ( $isAsso = true )
#end
#end
#end
#if ( $newInVersion || $isAsso )
#set ( $fields = $classToFields.get( $class ) )
#if ( ! $fields )
#set ( $fields = $LinkedHashSet.newInstance() )
#set ( $dummy = $classToFields.put( $class, $fields ) )
#end
#set( $dummy = $fields.add($field) )
#if ( $dummy )
#end
#end
#end
#end
#end
#end
#foreach ( $class in $classToFields.keySet() )
#set ( $var = $Helper.uncapitalise( $class.name ) )
private boolean is_${v}(${class.name} ${var}) {
return ${var} != null && (
#set ( $pfx = " " )
#if ( $class.superClass )
#if ( $classToFields.containsKey( $model.getClass( $class.superClass, $version ) ) )
$pfx is_${v}((${class.superClass}) ${var})
#set ( $pfx = "||" )
#end
#end
#foreach ( $field in $classToFields.get( $class ) )
#if ( $field.isManyMultiplicity() )
#if ( $classToFields.containsKey( $model.getClass( $field.type ) ) )
$pfx ${var}.get${Helper.capitalise($field.name)}().stream().anyMatch(this::is_${v}) // ${class.name} : ${field.name}
#else
$pfx !${var}.get${Helper.capitalise($field.name)}().isEmpty() // ${class.name} : ${field.name}
#end
#elseif ( $field.isOneMultiplicity() )
$pfx is_${v}(${var}.get${Helper.capitalise($field.name)}()) // ${class.name} : ${field.name}
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
$pfx has(${var}.is${Helper.capitalise($field.name)}()) // ${class.name} : ${field.name}
#else
$pfx has(${var}.get${Helper.capitalise($field.name)}()) // ${class.name} : ${field.name}
#end
#set ( $pfx = "||" )
#end
);
}
#end
#end
private boolean has(String str) {
return str != null;
}
private boolean has(Path path) {
return path != null;
}
private boolean has(boolean bool) {
return bool;
}
private boolean has(int val) {
return val != 0;
}
private boolean has(List<?> list) {
return !list.isEmpty();
}
}

View File

@ -84,10 +84,25 @@ public class ${className} {
//--------------------------/ //--------------------------/
/** /**
* Field NAMESPACE. * Default namespace.
*/ */
private static final String NAMESPACE = "${namespace}"; private static final String NAMESPACE = "${namespace}";
/**
* Default schemaLocation.
*/
private static final String SCHEMA_LOCATION = "${schemaLocation}";
/**
* Field namespace.
*/
private String namespace = NAMESPACE;
/**
* Field schemaLocation.
*/
private String schemaLocation = SCHEMA_LOCATION;
/** /**
* Field fileComment. * Field fileComment.
*/ */
@ -106,6 +121,24 @@ public class ${className} {
//- Methods -/ //- Methods -/
//-----------/ //-----------/
/**
* Method setNamespace.
*
* @param namespace the namespace to use.
*/
public void setNamespace(String namespace) {
this.namespace = Objects.requireNonNull(namespace);
} //-- void setNamespace(String)
/**
* Method setSchemaLocation.
*
* @param schemaLocation the schema location to use.
*/
public void setSchemaLocation(String schemaLocation) {
this.schemaLocation = Objects.requireNonNull(schemaLocation);
} //-- void setSchemaLocation(String)
/** /**
* Method setFileComment. * Method setFileComment.
* *
@ -183,12 +216,12 @@ public class ${className} {
serializer.writeComment(this.fileComment); serializer.writeComment(this.fileComment);
serializer.writeCharacters("\n"); serializer.writeCharacters("\n");
} }
serializer.writeStartElement("", tagName, NAMESPACE); serializer.writeStartElement("", tagName, namespace);
serializer.writeNamespace("", NAMESPACE); serializer.writeNamespace("", namespace);
serializer.writeNamespace("xsi", W3C_XML_SCHEMA_INSTANCE_NS_URI); serializer.writeNamespace("xsi", W3C_XML_SCHEMA_INSTANCE_NS_URI);
serializer.writeAttribute(W3C_XML_SCHEMA_INSTANCE_NS_URI, "schemaLocation", NAMESPACE + " ${schemaLocation}"); serializer.writeAttribute(W3C_XML_SCHEMA_INSTANCE_NS_URI, "schemaLocation", namespace + " " + schemaLocation);
#else #else
serializer.writeStartElement(NAMESPACE, tagName); serializer.writeStartElement(namespace, tagName);
#end #end
#foreach ( $field in $allFields ) #foreach ( $field in $allFields )
#if ( $Helper.xmlFieldMetadata( $field ).attribute ) #if ( $Helper.xmlFieldMetadata( $field ).attribute )
@ -283,7 +316,7 @@ public class ${className} {
#end #end
if (list != null && !list.isEmpty()) { if (list != null && !list.isEmpty()) {
if (!flat) { if (!flat) {
serializer.writeStartElement(NAMESPACE, tagName); serializer.writeStartElement(namespace, tagName);
} }
int index = 0; int index = 0;
#if ( $locationTracking ) #if ( $locationTracking )
@ -307,7 +340,7 @@ public class ${className} {
private <T> void writeProperties(String tagName, Map<String, String> props, XMLStreamWriter serializer) throws IOException, XMLStreamException { private <T> void writeProperties(String tagName, Map<String, String> props, XMLStreamWriter serializer) throws IOException, XMLStreamException {
#end #end
if (props != null && !props.isEmpty()) { if (props != null && !props.isEmpty()) {
serializer.writeStartElement(NAMESPACE, tagName); serializer.writeStartElement(namespace, tagName);
#if ( $locationTracking ) #if ( $locationTracking )
InputLocation location = locationTracker != null ? locationTracker.getLocation(tagName) : null; InputLocation location = locationTracker != null ? locationTracker.getLocation(tagName) : null;
#end #end
@ -326,7 +359,7 @@ public class ${className} {
private void writeDom(XmlNode dom, XMLStreamWriter serializer) throws IOException, XMLStreamException { private void writeDom(XmlNode dom, XMLStreamWriter serializer) throws IOException, XMLStreamException {
if (dom != null) { if (dom != null) {
serializer.writeStartElement(NAMESPACE, dom.getName()); serializer.writeStartElement(namespace, dom.getName());
for (Map.Entry<String, String> attr : dom.getAttributes().entrySet()) { for (Map.Entry<String, String> attr : dom.getAttributes().entrySet()) {
if (attr.getKey().startsWith("xml:")) { if (attr.getKey().startsWith("xml:")) {
serializer.writeAttribute("http://www.w3.org/XML/1998/namespace", serializer.writeAttribute("http://www.w3.org/XML/1998/namespace",
@ -357,7 +390,7 @@ public class ${className} {
private void writeTag(String tagName, String defaultValue, String value, XMLStreamWriter serializer) throws IOException, XMLStreamException { private void writeTag(String tagName, String defaultValue, String value, XMLStreamWriter serializer) throws IOException, XMLStreamException {
#end #end
if (value != null && !Objects.equals(defaultValue, value)) { if (value != null && !Objects.equals(defaultValue, value)) {
serializer.writeStartElement(NAMESPACE, tagName); serializer.writeStartElement(namespace, tagName);
serializer.writeCharacters(value); serializer.writeCharacters(value);
serializer.writeEndElement(); serializer.writeEndElement();
#if ( $locationTracking ) #if ( $locationTracking )