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: + *

+ *

    + *
  • string != null + *
+ */ + 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: + *

+ *

    + *
  • string != null + *
+ */ + 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: + *

+ *

    + *
  • string != null + *
+ */ + 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