diff --git a/maven-bom/pom.xml b/maven-bom/pom.xml
index 0e43dc90a987..1b8b9d255d74 100644
--- a/maven-bom/pom.xml
+++ b/maven-bom/pom.xml
@@ -142,6 +142,11 @@ under the License.
maven-repository-metadata
${project.version}
+
+ org.apache.maven
+ maven-repository-metadata-builder
+ ${project.version}
+
org.apache.maven
maven-resolver-provider
diff --git a/maven-core/pom.xml b/maven-core/pom.xml
index 6b20529582d3..6f801d22c80b 100644
--- a/maven-core/pom.xml
+++ b/maven-core/pom.xml
@@ -59,6 +59,10 @@ under the License.
org.apache.maven
maven-repository-metadata
+
+ org.apache.maven
+ maven-repository-metadata-builder
+
org.apache.maven
maven-artifact
diff --git a/maven-core/src/main/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolver.java b/maven-core/src/main/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolver.java
index 8bcea4968534..b02c9169185c 100644
--- a/maven-core/src/main/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolver.java
+++ b/maven-core/src/main/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolver.java
@@ -30,6 +30,8 @@
import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator.Level;
import org.apache.maven.model.Build;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.BuildPluginManager;
@@ -38,6 +40,7 @@
import org.apache.maven.plugin.prefix.PluginPrefixRequest;
import org.apache.maven.plugin.prefix.PluginPrefixResolver;
import org.apache.maven.plugin.prefix.PluginPrefixResult;
+import org.apache.maven.repository.internal.RepositoryListenerMetadataProblemCollector;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositoryEvent.EventType;
@@ -68,13 +71,18 @@ public class DefaultPluginPrefixResolver implements PluginPrefixResolver {
private final BuildPluginManager pluginManager;
private final RepositorySystem repositorySystem;
private final MetadataReader metadataReader;
+ private final MetadataValidator metadataValidator;
@Inject
public DefaultPluginPrefixResolver(
- BuildPluginManager pluginManager, RepositorySystem repositorySystem, MetadataReader metadataReader) {
+ BuildPluginManager pluginManager,
+ RepositorySystem repositorySystem,
+ MetadataReader metadataReader,
+ MetadataValidator metadataValidator) {
this.pluginManager = pluginManager;
this.repositorySystem = repositorySystem;
this.metadataReader = metadataReader;
+ this.metadataValidator = metadataValidator;
}
public PluginPrefixResult resolve(PluginPrefixRequest request) throws NoPluginFoundForPrefixException {
@@ -230,7 +238,12 @@ private PluginPrefixResult resolveFromRepository(
Map options = Collections.singletonMap(MetadataReader.IS_STRICT, Boolean.FALSE);
Metadata pluginGroupMetadata = metadataReader.read(metadata.getFile(), options);
-
+ metadataValidator.validate(
+ pluginGroupMetadata,
+ Level.GROUP_ID,
+ null,
+ new RepositoryListenerMetadataProblemCollector(
+ request.getRepositorySession(), repository, trace, metadata));
List plugins = pluginGroupMetadata.getPlugins();
if (plugins != null) {
diff --git a/maven-core/src/main/java/org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver.java b/maven-core/src/main/java/org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver.java
index 904606a2a04a..30506f2c93b0 100644
--- a/maven-core/src/main/java/org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver.java
+++ b/maven-core/src/main/java/org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver.java
@@ -36,6 +36,8 @@
import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator.Level;
import org.apache.maven.model.Build;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MavenPluginManager;
@@ -45,6 +47,7 @@
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.plugin.version.PluginVersionResult;
+import org.apache.maven.repository.internal.RepositoryListenerMetadataProblemCollector;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositoryEvent.EventType;
import org.eclipse.aether.RepositoryListener;
@@ -78,6 +81,7 @@ public class DefaultPluginVersionResolver implements PluginVersionResolver {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final RepositorySystem repositorySystem;
private final MetadataReader metadataReader;
+ private final MetadataValidator metadataValidator;
private final MavenPluginManager pluginManager;
private final VersionScheme versionScheme;
@@ -85,10 +89,12 @@ public class DefaultPluginVersionResolver implements PluginVersionResolver {
public DefaultPluginVersionResolver(
RepositorySystem repositorySystem,
MetadataReader metadataReader,
+ MetadataValidator metadataValidator,
MavenPluginManager pluginManager,
VersionScheme versionScheme) {
this.repositorySystem = repositorySystem;
this.metadataReader = metadataReader;
+ this.metadataValidator = metadataValidator;
this.pluginManager = pluginManager;
this.versionScheme = versionScheme;
}
@@ -280,7 +286,11 @@ private void mergeMetadata(
Map options = Collections.singletonMap(MetadataReader.IS_STRICT, Boolean.FALSE);
Metadata repoMetadata = metadataReader.read(metadata.getFile(), options);
-
+ metadataValidator.validate(
+ repoMetadata,
+ Level.GROUP_ID,
+ null,
+ new RepositoryListenerMetadataProblemCollector(session, repository, trace, metadata));
mergeMetadata(versions, repoMetadata, repository);
} catch (IOException e) {
invalidMetadata(session, trace, metadata, repository, e);
diff --git a/maven-repository-metadata-builder/pom.xml b/maven-repository-metadata-builder/pom.xml
new file mode 100644
index 000000000000..988e9c8af861
--- /dev/null
+++ b/maven-repository-metadata-builder/pom.xml
@@ -0,0 +1,60 @@
+
+
+
+ 4.0.0
+
+
+ org.apache.maven
+ maven
+ 4.0.0-alpha-8-SNAPSHOT
+
+
+ maven-repository-metadata-builder
+
+ Maven Repository Metadata Builder
+ The repository metadata builder
+
+
+
+ org.apache.maven
+ maven-repository-metadata
+
+
+
+
+ javax.inject
+ javax.inject
+
+
+
+
+
+
+ org.eclipse.sisu
+ sisu-maven-plugin
+
+
+
+
+
diff --git a/maven-repository-metadata/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Reader.java b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Reader.java
similarity index 100%
rename from maven-repository-metadata/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Reader.java
rename to maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Reader.java
diff --git a/maven-repository-metadata/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Writer.java b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Writer.java
similarity index 100%
rename from maven-repository-metadata/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Writer.java
rename to maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Writer.java
diff --git a/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/DefaultMetadataValidator.java b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/DefaultMetadataValidator.java
new file mode 100644
index 000000000000..5a7ea47ac63e
--- /dev/null
+++ b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/DefaultMetadataValidator.java
@@ -0,0 +1,240 @@
+/*
+ * 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.artifact.repository.metadata.validator;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.Collection;
+
+import org.apache.maven.artifact.repository.metadata.Metadata;
+import org.apache.maven.artifact.repository.metadata.Plugin;
+import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataProblemCollector.Severity;
+
+/**
+ * Validates repository metadata on different levels.
+ *
+ * @see Repository Metadata Model
+ * @see Repository Metadata Descriptor
+ */
+@Named
+@Singleton
+public class DefaultMetadataValidator implements MetadataValidator {
+
+ @Override
+ public void validate(Metadata metadata, MetadataProblemCollector problems) {
+ // some heuristic to determine level
+ final Level level;
+ if (!metadata.getPlugins().isEmpty()) {
+ level = Level.GROUP_ID;
+ } else if (metadata.getVersion() != null) {
+ level = Level.VERSION;
+ } else {
+ level = Level.ARTIFACT_ID;
+ }
+ validate(metadata, level, null, problems);
+ }
+
+ @Override
+ public void validate(Metadata metadata, Level level, Boolean isSnapshot, MetadataProblemCollector problems) {
+ switch (level) {
+ case GROUP_ID:
+ for (Plugin plugin : metadata.getPlugins()) {
+ validateStringNotEmpty(
+ problems, "plugins.plugin.name", plugin.getName(), plugin.getArtifactId(), level);
+ validateStringNotEmpty(
+ problems, "plugins.plugin.prefix", plugin.getPrefix(), plugin.getName(), level);
+ validateStringNotEmpty(
+ problems, "plugins.plugin.artifactId", plugin.getArtifactId(), plugin.getName(), level);
+ }
+ validateNullOrEmptyCollection(problems, "groupId", metadata.getGroupId(), null, level);
+ validateNullOrEmptyCollection(problems, "artifactId", metadata.getArtifactId(), null, level);
+ validateNullOrEmptyCollection(problems, "version", metadata.getVersion(), null, level);
+ validateNullOrEmptyCollection(problems, "versioning", metadata.getVersioning(), null, level);
+ break;
+ case ARTIFACT_ID:
+ validateStringNotEmpty(problems, "groupId", metadata.getGroupId(), null, level);
+ validateStringNotEmpty(problems, "artifactId", metadata.getArtifactId(), null, level);
+ validateNullOrEmptyCollection(problems, "version", metadata.getVersion(), null, level);
+ if (validateNotNull(problems, "versioning", metadata.getVersioning(), null, level)) {
+ validateNotEmptyCollection(
+ problems,
+ "versioning.versions",
+ metadata.getVersioning().getVersions(),
+ null,
+ level);
+ validateNullOrEmptyCollection(
+ problems,
+ "versioning.snapshotVersions",
+ metadata.getVersioning().getSnapshotVersions(),
+ null,
+ level);
+ }
+ validateNullOrEmptyCollection(problems, "plugins", metadata.getPlugins(), null, level);
+ // TODO: release or latest is mandatory?
+ break;
+ default:
+ if (isSnapshot == Boolean.FALSE) {
+ validateNullOrEmptyCollection(problems, "groupId", metadata.getGroupId(), null, level);
+ validateNullOrEmptyCollection(problems, "artifactId", metadata.getArtifactId(), null, level);
+ validateNullOrEmptyCollection(problems, "version", metadata.getVersion(), null, level);
+ validateNullOrEmptyCollection(problems, "versioning", metadata.getVersioning(), null, level);
+
+ } else {
+ validateStringNotEmpty(problems, "groupId", metadata.getGroupId(), null, level);
+ validateStringNotEmpty(problems, "artifactId", metadata.getArtifactId(), null, level);
+ validateStringNotEmpty(problems, "version", metadata.getArtifactId(), null, level);
+ if (validateNotNull(problems, "versioning", metadata.getVersioning(), null, level)) {
+ for (SnapshotVersion version : metadata.getVersioning().getSnapshotVersions()) {
+ validateStringNotEmpty(
+ problems,
+ "versioning.snapshotVersions.snapshotVersion.extension",
+ version.getExtension(),
+ version.getVersion(),
+ level);
+ validateStringNotEmpty(
+ problems,
+ "versioning.snapshotVersions.snapshotVersion.value",
+ version.getVersion(),
+ null,
+ level);
+ // TODO validate updated timestamp?
+ }
+ validateNotNull(
+ problems,
+ "versioning.snapshotVersions",
+ metadata.getVersioning().getSnapshotVersions(),
+ null,
+ level);
+ }
+ }
+ validateNullOrEmptyCollection(problems, "plugins", metadata.getPlugins(), null, level);
+ break;
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Field validation
+ // ----------------------------------------------------------------------
+
+ /**
+ * Asserts:
+ *
+ *
+ * string.length != null
+ * string.length > 0
+ *
+ */
+ private static boolean validateStringNotEmpty(
+ MetadataProblemCollector problems, String fieldName, String string, String sourceHint, Level level) {
+ if (!validateNotNull(problems, fieldName, string, sourceHint, level)) {
+ return false;
+ }
+
+ if (string.length() > 0) {
+ return true;
+ }
+
+ addViolation(problems, Severity.ERROR, fieldName, sourceHint, level, "is missing");
+
+ return false;
+ }
+
+ /**
+ * Asserts:
+ *
+ *
+ */
+ private static boolean validateNotNull(
+ MetadataProblemCollector problems, String fieldName, Object object, String sourceHint, Level level) {
+ if (object != null) {
+ return true;
+ }
+
+ addViolation(problems, Severity.ERROR, fieldName, sourceHint, level, "is missing");
+
+ return false;
+ }
+
+ /**
+ * Asserts:
+ *
+ *
+ */
+ private static boolean validateNullOrEmptyCollection(
+ MetadataProblemCollector problems, String fieldName, Object object, String sourceHint, Level level) {
+ if (object == null
+ || (object instanceof Collection
+ && Collection.class.cast(object).isEmpty())) {
+ return true;
+ }
+
+ addViolation(problems, Severity.WARNING, fieldName, sourceHint, level, "is unused");
+
+ return false;
+ }
+
+ /**
+ * Asserts:
+ *
+ *
+ */
+ private static boolean validateNotEmptyCollection(
+ MetadataProblemCollector problems,
+ String fieldName,
+ Collection> collection,
+ String sourceHint,
+ Level level) {
+ if (!collection.isEmpty()) {
+ return true;
+ }
+
+ addViolation(problems, Severity.WARNING, fieldName, sourceHint, level, "is empty collection");
+
+ return false;
+ }
+
+ private static void addViolation(
+ MetadataProblemCollector problems,
+ Severity severity,
+ String fieldName,
+ String sourceHint,
+ Level level,
+ String message) {
+ StringBuilder buffer = new StringBuilder(256);
+ buffer.append('\'').append(fieldName).append('\'');
+
+ if (sourceHint != null) {
+ buffer.append(" for ").append(sourceHint);
+ }
+ if (level != null) {
+ buffer.append(" on repository metadata level ").append(level);
+ }
+ buffer.append(' ').append(message);
+
+ problems.add(severity, buffer.toString(), -1, -1, null);
+ }
+}
diff --git a/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/MetadataProblemCollector.java b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/MetadataProblemCollector.java
new file mode 100644
index 000000000000..b4e2b9a9045a
--- /dev/null
+++ b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/MetadataProblemCollector.java
@@ -0,0 +1,45 @@
+/*
+ * 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.artifact.repository.metadata.validator;
+
+/**
+ * Collects problems that are encountered during reading repository metadata.
+ */
+public interface MetadataProblemCollector {
+
+ /**
+ * The different severity levels for a problem, in decreasing order.
+ */
+ enum Severity {
+ FATAL, //
+ ERROR, //
+ WARNING //
+ }
+
+ /**
+ * Adds the specified problem.
+ *
+ * @param severity The severity of the problem, must not be {@code null}.
+ * @param message The detail message of the problem, may be {@code null}.
+ * @param line The one-based index of the line containing the problem or {@code -1} if unknown.
+ * @param column The one-based index of the column containing the problem or {@code -1} if unknown.
+ * @param cause The cause of the problem, may be {@code null}.
+ */
+ void add(Severity severity, String message, int line, int column, Exception cause);
+}
diff --git a/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/MetadataValidator.java b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/MetadataValidator.java
new file mode 100644
index 000000000000..4023acc7e1a2
--- /dev/null
+++ b/maven-repository-metadata-builder/src/main/java/org/apache/maven/artifact/repository/metadata/validator/MetadataValidator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.artifact.repository.metadata.validator;
+
+import org.apache.maven.artifact.repository.metadata.Metadata;
+
+/**
+ * Validates repository metadata
+ *
+ */
+public interface MetadataValidator {
+
+ /**
+ * The different levels where repository metadata may occur in the
+ * repository layout.
+ */
+ enum Level {
+ GROUP_ID("groupId"),
+ ARTIFACT_ID("artifactId"),
+ VERSION("version");
+
+ private final String label;
+
+ Level(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+ }
+
+ /**
+ * Validates the specified metadata with unknown level.
+ * Applies some heuristic to determine which repository metadata level is probably specified through the given
+ * metadata
+ *
+ * @param metadata The metadata to validate, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ void validate(Metadata metadata, MetadataProblemCollector problems);
+
+ /**
+ * Validates the specified metadata for the given level.
+ *
+ * @param metadata The metadata to validate, must not be {@code null}.
+ * @param level The metadata level, must not be {@code null}.
+ * @param isSnapshot {@code false} in case the metadata is only for snapshot versions, {@code true} in case it is
+ * only for release versions or {@code null} if it is potentially for both snapshot and release versions.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ void validate(Metadata metadata, Level level, Boolean isSnapshot, MetadataProblemCollector problems);
+}
diff --git a/maven-repository-metadata-builder/src/test/java/org/apache/maven/artifact/repository/metadata/validation/DefaultMetadataValidatorTest.java b/maven-repository-metadata-builder/src/test/java/org/apache/maven/artifact/repository/metadata/validation/DefaultMetadataValidatorTest.java
new file mode 100644
index 000000000000..fe163a1c5c78
--- /dev/null
+++ b/maven-repository-metadata-builder/src/test/java/org/apache/maven/artifact/repository/metadata/validation/DefaultMetadataValidatorTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.artifact.repository.metadata.validation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.artifact.repository.metadata.Metadata;
+import org.apache.maven.artifact.repository.metadata.Plugin;
+import org.apache.maven.artifact.repository.metadata.Versioning;
+import org.apache.maven.artifact.repository.metadata.validator.DefaultMetadataValidator;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataProblemCollector;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator.Level;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DefaultMetadataValidatorTest {
+
+ private DefaultMetadataValidator validator;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ validator = new DefaultMetadataValidator();
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception {
+ validator = null;
+ }
+
+ private void assertContains(String msg, String substring) {
+ assertTrue(msg.contains(substring), "\"" + substring + "\" was not found in: " + msg);
+ }
+
+ @Test
+ void testValidateArtifactMetadata() {
+ Metadata metadata = new Metadata();
+ SimpleProblemCollector problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(3, problems.messages.size());
+ assertContains(problems.messages.get(0), "'groupId' on repository metadata level artifactId is missing");
+ assertContains(problems.messages.get(1), "'artifactId' on repository metadata level artifactId is missing");
+ assertContains(problems.messages.get(2), "'versioning' on repository metadata level artifactId is missing");
+
+ metadata.setArtifactId("myArtifactId");
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(2, problems.messages.size());
+
+ metadata.setGroupId("myGroupId");
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(1, problems.messages.size());
+
+ Versioning versioning = new Versioning();
+ metadata.setVersioning(versioning);
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(1, problems.messages.size());
+
+ versioning.addVersion("1.2.3");
+ ;
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(0, problems.messages.size());
+ }
+
+ @Test
+ void testValidateGroupIdMetadata() {
+ Metadata metadata = new Metadata();
+ SimpleProblemCollector problems = new SimpleProblemCollector();
+ validator.validate(metadata, Level.GROUP_ID, null, problems);
+ assertEquals(0, problems.messages.size());
+
+ Plugin plugin = new Plugin();
+ plugin.setArtifactId("myArtifactId");
+ plugin.setName("myPluginName");
+ plugin.setPrefix("mypluginprefix");
+ metadata.getPlugins().add(plugin);
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(0, problems.messages.size());
+
+ plugin = new Plugin();
+ metadata.getPlugins().add(plugin);
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(3, problems.messages.size());
+ assertContains(
+ problems.messages.get(0), "'plugins.plugin.name' on repository metadata level groupId is missing");
+ assertContains(
+ problems.messages.get(1), "'plugins.plugin.prefix' on repository metadata level groupId is missing");
+ assertContains(
+ problems.messages.get(2),
+ "'plugins.plugin.artifactId' on repository metadata level groupId is missing");
+
+ plugin = metadata.getPlugins().get(1);
+ plugin.setArtifactId("myOtherArtifactId");
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(2, problems.messages.size());
+ assertContains(
+ problems.messages.get(0),
+ "'plugins.plugin.name' for myOtherArtifactId on repository metadata level groupId is missing");
+ assertContains(
+ problems.messages.get(1), "'plugins.plugin.prefix' on repository metadata level groupId is missing");
+
+ plugin.setName("myOtherPluginName");
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(1, problems.messages.size());
+ assertContains(
+ problems.messages.get(0),
+ "'plugins.plugin.prefix' for myOtherPluginName on repository metadata level groupId is missing");
+
+ plugin.setPrefix("myotherpluginprefix");
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ validator.validate(metadata, Level.GROUP_ID, null, problems);
+ assertEquals(0, problems.messages.size());
+
+ metadata.setGroupId("myGroupId");
+ problems = new SimpleProblemCollector();
+ validator.validate(metadata, problems);
+ assertEquals(1, problems.messages.size());
+ }
+
+ @Test
+ void testValidateVersionMetadata() {
+ // TODO: add some tests
+ }
+
+ private static class SimpleProblemCollector implements MetadataProblemCollector {
+
+ public List messages = new ArrayList<>();
+
+ public void add(Severity severity, String message, int line, int column, Exception cause) {
+ messages.add(message);
+ }
+ }
+}
diff --git a/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java b/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java
index b2d6d7842bc4..7606a452e72c 100644
--- a/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java
+++ b/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java
@@ -35,6 +35,7 @@
import org.apache.maven.artifact.repository.metadata.io.MetadataStaxWriter;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -235,6 +236,46 @@ void testRoundtrip() throws Exception {
assertNotNull(source2);
}
+ @Test
+ void testMergeWithEmptyMetadata() {
+ Metadata metadata = new Metadata();
+ Metadata newMetadata = new Metadata();
+ Assertions.assertFalse(metadata.merge(newMetadata));
+ }
+
+ @Test
+ void testMergeWithDifferentPrefixes() {
+ Metadata metadata = new Metadata();
+ Plugin plugin = new Plugin();
+ plugin.setArtifactId("myArtifactId");
+ plugin.setName("My Name");
+ plugin.setPrefix("myprefix");
+ metadata.addPlugin(plugin);
+
+ Metadata newMetadata = new Metadata();
+ Plugin newPlugin = plugin.clone();
+ newPlugin.setArtifactId("myNewArtifactId");
+ plugin.setPrefix("mynewprefix");
+ newMetadata.addPlugin(newPlugin);
+ Assertions.assertTrue(metadata.merge(newMetadata));
+ }
+
+ @Test
+ void testMergeWithSamePrefixAndDifferentArtifactIds() {
+ Metadata metadata = new Metadata();
+ Plugin plugin = new Plugin();
+ plugin.setArtifactId("myArtifactId");
+ plugin.setName("My Name");
+ plugin.setPrefix("myprefix");
+ metadata.addPlugin(plugin);
+
+ Metadata newMetadata = new Metadata();
+ Plugin newPlugin = plugin.clone();
+ newPlugin.setArtifactId("myNewArtifactId");
+ newMetadata.addPlugin(newPlugin);
+ Assertions.assertFalse(metadata.merge(newMetadata));
+ }
+
/*-- START helper methods to populate metadata objects ---*/
private static final String SNAPSHOT = "SNAPSHOT";
diff --git a/maven-resolver-provider/pom.xml b/maven-resolver-provider/pom.xml
index decd6d879d7a..f1d5f3aa01ca 100644
--- a/maven-resolver-provider/pom.xml
+++ b/maven-resolver-provider/pom.xml
@@ -52,6 +52,10 @@ under the License.
org.apache.maven
maven-repository-metadata
+
+ org.apache.maven
+ maven-repository-metadata-builder
+
org.apache.maven.resolver
maven-resolver-api
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
index c8ca6bd374d6..39dcac5935f3 100644
--- a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
@@ -34,6 +34,8 @@
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.MetadataStaxReader;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator.Level;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositoryEvent.EventType;
import org.eclipse.aether.RepositorySystemSession;
@@ -68,6 +70,7 @@ public class DefaultVersionRangeResolver implements VersionRangeResolver {
private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
private final MetadataResolver metadataResolver;
+ private final MetadataValidator metadataValidator;
private final SyncContextFactory syncContextFactory;
private final RepositoryEventDispatcher repositoryEventDispatcher;
private final VersionScheme versionScheme;
@@ -75,10 +78,12 @@ public class DefaultVersionRangeResolver implements VersionRangeResolver {
@Inject
public DefaultVersionRangeResolver(
MetadataResolver metadataResolver,
+ MetadataValidator metadataValidator,
SyncContextFactory syncContextFactory,
RepositoryEventDispatcher repositoryEventDispatcher,
VersionScheme versionScheme) {
this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
+ this.metadataValidator = Objects.requireNonNull(metadataValidator, "metadataValidator cannot be null");
this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
this.repositoryEventDispatcher =
Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
@@ -204,8 +209,16 @@ private Versioning readVersions(
if (metadata.getFile() != null && metadata.getFile().exists()) {
try (InputStream in =
Files.newInputStream(metadata.getFile().toPath())) {
- versioning = new Versioning(
- new MetadataStaxReader().read(in, false).getVersioning());
+ org.apache.maven.artifact.repository.metadata.Metadata mavenMetadata =
+ new org.apache.maven.artifact.repository.metadata.Metadata(
+ new MetadataStaxReader().read(in, false));
+ metadataValidator.validate(
+ mavenMetadata,
+ Level.ARTIFACT_ID,
+ null,
+ new RepositoryEventDispatcherMetadataProblemCollector(
+ session, repository, repositoryEventDispatcher, trace, metadata));
+ versioning = mavenMetadata.getVersioning();
}
}
}
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionResolver.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionResolver.java
index 4b54afc8bd5c..dafe40a8ce2f 100644
--- a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionResolver.java
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionResolver.java
@@ -37,6 +37,8 @@
import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.MetadataStaxReader;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator;
+import org.apache.maven.artifact.repository.metadata.validator.MetadataValidator.Level;
import org.eclipse.aether.RepositoryCache;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositoryEvent.EventType;
@@ -77,15 +79,18 @@ public class DefaultVersionResolver implements VersionResolver {
private static final String SNAPSHOT = "SNAPSHOT";
private final MetadataResolver metadataResolver;
+ private final MetadataValidator metadataValidator;
private final SyncContextFactory syncContextFactory;
private final RepositoryEventDispatcher repositoryEventDispatcher;
@Inject
public DefaultVersionResolver(
MetadataResolver metadataResolver,
+ MetadataValidator metadataValidator,
SyncContextFactory syncContextFactory,
RepositoryEventDispatcher repositoryEventDispatcher) {
this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
+ this.metadataValidator = Objects.requireNonNull(metadataValidator, "metadataValidator cannot be null");
this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
this.repositoryEventDispatcher =
Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
@@ -246,8 +251,16 @@ private Versioning readVersions(
if (metadata.getFile() != null && metadata.getFile().exists()) {
try (InputStream in =
Files.newInputStream(metadata.getFile().toPath())) {
- versioning = new Versioning(
- new MetadataStaxReader().read(in, false).getVersioning());
+ org.apache.maven.artifact.repository.metadata.Metadata mavenMetadata =
+ new org.apache.maven.artifact.repository.metadata.Metadata(
+ new MetadataStaxReader().read(in, false));
+ metadataValidator.validate(
+ mavenMetadata,
+ Level.VERSION,
+ null,
+ new RepositoryEventDispatcherMetadataProblemCollector(
+ session, repository, repositoryEventDispatcher, trace, metadata));
+ versioning = mavenMetadata.getVersioning();
/*
NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RepositoryEventDispatcherMetadataProblemCollector.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RepositoryEventDispatcherMetadataProblemCollector.java
new file mode 100644
index 000000000000..6f55b1bfa055
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RepositoryEventDispatcherMetadataProblemCollector.java
@@ -0,0 +1,48 @@
+/*
+ * 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.repository.internal;
+
+import java.util.Objects;
+
+import org.eclipse.aether.RepositoryEvent;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.RequestTrace;
+import org.eclipse.aether.impl.RepositoryEventDispatcher;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.ArtifactRepository;
+
+public class RepositoryEventDispatcherMetadataProblemCollector extends RepositoryListenerMetadataProblemCollector {
+
+ private final RepositoryEventDispatcher repositoryEventDispatcher;
+
+ public RepositoryEventDispatcherMetadataProblemCollector(
+ RepositorySystemSession session,
+ ArtifactRepository repository,
+ RepositoryEventDispatcher repositoryEventDispatcher,
+ RequestTrace trace,
+ Metadata metadata) {
+ super(session, repository, trace, metadata);
+ this.repositoryEventDispatcher =
+ Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
+ }
+
+ void dispatchEvent(RepositoryEvent event) {
+ repositoryEventDispatcher.dispatch(event);
+ }
+}
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RepositoryListenerMetadataProblemCollector.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RepositoryListenerMetadataProblemCollector.java
new file mode 100644
index 000000000000..1688b593dd9b
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RepositoryListenerMetadataProblemCollector.java
@@ -0,0 +1,72 @@
+/*
+ * 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.repository.internal;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import org.apache.maven.artifact.repository.metadata.validator.MetadataProblemCollector;
+import org.eclipse.aether.RepositoryEvent;
+import org.eclipse.aether.RepositoryEvent.EventType;
+import org.eclipse.aether.RepositoryListener;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.RequestTrace;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.ArtifactRepository;
+
+public class RepositoryListenerMetadataProblemCollector implements MetadataProblemCollector {
+
+ private final RepositorySystemSession session;
+ private final ArtifactRepository repository;
+ private final RequestTrace trace;
+ private final Metadata metadata;
+
+ public RepositoryListenerMetadataProblemCollector(
+ RepositorySystemSession session, ArtifactRepository repository, RequestTrace trace, Metadata metadata) {
+ this.session = Objects.requireNonNull(session, "session cannot be null");
+ this.repository = Objects.requireNonNull(repository, "repository cannot be null");
+ this.trace = trace;
+ this.metadata = Objects.requireNonNull(metadata, "metadata cannot be null");
+ }
+
+ private RepositoryEvent createMetadataInvalidEvent(Exception exception) {
+ RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
+ event.setTrace(trace);
+ event.setMetadata(metadata);
+ event.setException(exception);
+ event.setRepository(repository);
+ return event.build();
+ }
+
+ void dispatchEvent(RepositoryEvent event) {
+ RepositoryListener listener = session.getRepositoryListener();
+ if (listener != null) {
+ listener.metadataInvalid(event);
+ }
+ }
+
+ @Override
+ public void add(Severity severity, String message, int line, int column, Exception cause) {
+ // TODO: when to break build?
+ // TODO: which exception to use?
+ Exception e = new IOException("Invalid metadata: " + message, cause);
+ RepositoryEvent event = createMetadataInvalidEvent(e);
+ dispatchEvent(event);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 95ba29dc5237..66a5f02090e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -113,6 +113,7 @@ under the License.
maven-artifact
maven-resolver-provider
maven-repository-metadata
+ maven-repository-metadata-builder
maven-slf4j-provider
maven-slf4j-wrapper
maven-embedder