From 3972cf353fffd7972f1fb293ab03f6afc8310d79 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Tue, 26 Mar 2024 17:24:53 +0100
Subject: [PATCH] [MNG-8084] Move ModelBuilder and resolver provider to v4 api
---
.../maven/api/services/ModelBuilder.java | 30 +
.../api/services/ModelBuilderException.java | 51 +
.../api/services/ModelBuilderRequest.java | 389 ++++
.../api/services/ModelBuilderResult.java | 119 ++
.../apache/maven/api/services/ModelCache.java | 36 +
.../maven/api/services/ModelProblem.java | 57 +
.../maven/api/services/ModelResolver.java | 124 ++
.../maven/api/services/ModelSource.java | 50 +
.../maven/api/services/PathModelSource.java | 40 +
.../apache/maven/api/services/PathSource.java | 8 +-
.../maven/api/services/TypeRegistry.java | 5 +-
.../org/apache/maven/api/spi/ModelParser.java | 5 +
maven-api-impl/pom.xml | 60 +-
.../model/DependencyManagementImporter.java | 46 +
.../model/DependencyManagementInjector.java | 39 +
.../services/model/InheritanceAssembler.java | 43 +
.../model/LifecycleBindingsInjector.java | 41 +
.../services/model/ModelBuildingEvent.java | 50 +
.../services/model/ModelBuildingListener.java | 33 +
.../api/services/model/ModelInterpolator.java | 47 +
.../api/services/model/ModelNormalizer.java | 49 +
.../services/model/ModelPathTranslator.java | 42 +
.../services/model/ModelProblemCollector.java | 65 +
.../api/services/model/ModelProcessor.java | 51 +
.../model/ModelSourceTransformer.java | 42 +
.../services/model/ModelUrlNormalizer.java | 38 +
.../api/services/model/ModelValidator.java | 60 +
.../services/model/ModelVersionParser.java | 70 +
.../services/model/ModelVersionProcessor.java | 44 +
.../api/services/model/PathTranslator.java | 40 +
.../model/PluginConfigurationExpander.java | 38 +
.../model/PluginManagementInjector.java | 39 +
.../model/ProfileActivationContext.java | 81 +
.../api/services/model/ProfileActivator.java | 53 +
.../api/services/model/ProfileInjector.java | 58 +
.../api/services/model/ProfileSelector.java | 44 +
.../maven/api/services/model/RootLocator.java | 67 +
.../services/model/TransformerContext.java | 67 +
.../model/TransformerContextBuilder.java | 45 +
.../services/model/TransformerException.java | 36 +
.../api/services/model/UrlNormalizer.java | 30 +
.../model/WorkspaceModelResolver.java | 31 +
.../maven/internal/impl/DefaultArtifact.java | 1 -
.../internal/impl/DefaultDependency.java | 1 -
.../impl/DefaultModelUrlNormalizer.java | 80 +
.../impl/DefaultModelVersionParser.java | 288 +++
.../DefaultPluginConfigurationExpander.java | 107 +
.../internal/impl/DefaultSettingsBuilder.java | 40 +-
.../impl/DefaultSuperPomProvider.java | 80 +
.../internal/impl/DefaultUrlNormalizer.java | 61 +
.../internal/impl/DefaultVersionParser.java | 2 +-
.../impl/DefaultVersionRangeResolver.java | 5 +-
.../impl/ExtensibleEnumRegistries.java | 20 +-
.../maven/internal/impl/InternalSession.java | 16 +
.../model/BuildModelSourceTransformer.java | 199 ++
.../DefaultDependencyManagementImporter.java | 147 ++
.../DefaultDependencyManagementInjector.java | 118 ++
.../model/DefaultInheritanceAssembler.java | 329 ++++
.../DefaultLifecycleBindingsInjector.java | 167 ++
.../impl/model/DefaultModelBuilder.java | 1382 +++++++++++++
.../impl/model/DefaultModelBuilderResult.java | 213 ++
.../impl/model/DefaultModelBuildingEvent.java | 30 +
.../impl/model/DefaultModelInterpolator.java | 484 +++++
.../impl/model/DefaultModelNormalizer.java | 146 ++
.../model/DefaultModelPathTranslator.java | 122 ++
.../impl/model/DefaultModelProblem.java | 182 ++
.../model/DefaultModelProblemCollector.java | 199 ++
.../impl/model/DefaultModelProcessor.java | 143 ++
.../impl/model/DefaultModelValidator.java | 1742 +++++++++++++++++
.../model/DefaultModelVersionProcessor.java | 64 +
.../impl/model/DefaultPathTranslator.java | 59 +
.../DefaultPluginManagementInjector.java | 128 ++
.../DefaultProfileActivationContext.java | 191 ++
.../impl/model/DefaultProfileInjector.java | 241 +++
.../impl/model/DefaultProfileSelector.java | 123 ++
.../impl/model/DefaultRootLocator.java | 62 +
.../impl/model/DefaultTransformerContext.java | 140 ++
.../DefaultTransformerContextBuilder.java | 224 +++
.../impl/model/FileToRawModelMerger.java | 216 ++
.../maven/internal/impl/model/Graph.java | 96 +
.../impl/model/MavenBuildTimestamp.java | 79 +
.../internal/impl/model/MavenModelMerger.java | 621 ++++++
.../maven/internal/impl/model/ModelData.java | 38 +-
.../impl/model/ModelProblemUtils.java | 130 ++
...ProfileActivationFilePathInterpolator.java | 100 +
.../maven/internal/impl/model/Result.java | 230 +++
.../ArtifactDescriptorReaderDelegate.java | 149 ++
.../resolver/ArtifactDescriptorUtils.java | 94 +
.../DefaultArtifactDescriptorReader.java | 303 +++
.../impl/resolver/DefaultModelCache.java | 210 ++
.../resolver/DefaultModelCacheFactory.java | 36 +
.../impl/resolver/DefaultModelResolver.java | 216 ++
.../resolver/DefaultVersionRangeResolver.java | 251 +++
.../impl/resolver/DefaultVersionResolver.java | 508 +++++
.../DefaultVersionSchemeProvider.java | 38 +
.../impl/resolver/LocalSnapshotMetadata.java | 142 ++
.../LocalSnapshotMetadataGenerator.java | 77 +
.../MavenArtifactRelocationSource.java | 45 +
.../internal/impl/resolver/MavenMetadata.java | 146 ++
.../resolver/MavenSessionBuilderSupplier.java | 140 ++
.../impl/resolver/MavenSnapshotMetadata.java | 84 +
.../impl/resolver/MavenWorkspaceReader.java | 31 +
.../impl/resolver/ModelCacheFactory.java | 30 +
.../impl/resolver/PluginsMetadata.java | 115 ++
.../resolver/PluginsMetadataGenerator.java | 146 ++
.../PluginsMetadataGeneratorFactory.java | 52 +
.../impl/resolver/RelocatedArtifact.java | 174 ++
.../impl/resolver/RemoteSnapshotMetadata.java | 148 ++
.../RemoteSnapshotMetadataGenerator.java | 108 +
.../SnapshotMetadataGeneratorFactory.java | 52 +
.../resolver/UnresolvableModelException.java | 27 +
.../impl/resolver/VersionsMetadata.java | 133 ++
.../resolver/VersionsMetadataGenerator.java | 103 +
.../VersionsMetadataGeneratorFactory.java | 52 +
.../artifact/FatArtifactTraverser.java | 67 +
.../artifact/MavenArtifactProperties.java | 56 +
.../internal/impl/resolver/package-info.java | 6 +
...ionManagementArtifactRelocationSource.java | 76 +
...serPropertiesArtifactRelocationSource.java | 215 ++
.../scopes/MavenDependencyContextRefiner.java | 84 +
.../scopes/MavenDependencyScopes.java | 53 +
.../resolver/scopes/MavenScopeDeriver.java | 59 +
.../resolver/scopes/MavenScopeSelector.java | 83 +
.../scopes/MavenSystemScopeHandler.java | 51 +
.../impl/resolver/type/DefaultType.java | 114 ++
.../resolver/type/DefaultTypeProvider.java | 67 +
.../org/apache/maven/model/pom-4.0.0.xml | 64 +
.../org/apache/maven/model/pom-4.1.0.xml | 64 +
.../impl/model}/MavenModelMergerTest.java | 2 +-
.../internal/impl/standalone/ApiRunner.java | 145 +-
.../internal/impl/standalone/DiTest.java | 38 +
.../standalone/RepositorySystemSupplier.java | 1031 ++++++++++
.../impl/standalone/TestApiStandalone.java | 9 +
...DefaultRepositorySystemSessionFactory.java | 9 +-
.../internal/impl/DefaultSessionFactory.java | 6 +-
.../internal/impl/SisuDiBridgeModule.java | 22 +-
maven-di/pom.xml | 12 +
.../apache/maven/di/impl/InjectorImpl.java | 26 +-
.../java/org/apache/maven/di/impl/Types.java | 20 +-
maven-model-builder/pom.xml | 4 +
.../maven/model/merge/MavenModelMerger.java | 594 +-----
141 files changed, 17274 insertions(+), 722 deletions(-)
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderException.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelCache.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelResolver.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelSource.java
create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/PathModelSource.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/LifecycleBindingsInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingEvent.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingListener.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProblemCollector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProcessor.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelSourceTransformer.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionParser.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionProcessor.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/PathTranslator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/RootLocator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContext.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContextBuilder.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerException.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/UrlNormalizer.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/api/services/model/WorkspaceModelResolver.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelUrlNormalizer.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelVersionParser.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultPluginConfigurationExpander.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSuperPomProvider.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultUrlNormalizer.java
rename {maven-core => maven-api-impl}/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java (86%)
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/BuildModelSourceTransformer.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementImporter.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultInheritanceAssembler.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultLifecycleBindingsInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilderResult.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuildingEvent.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelInterpolator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelNormalizer.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelPathTranslator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblem.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblemCollector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProcessor.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelVersionProcessor.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultPathTranslator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultPluginManagementInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileActivationContext.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileInjector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileSelector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultRootLocator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultTransformerContext.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultTransformerContextBuilder.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/FileToRawModelMerger.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/Graph.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/MavenBuildTimestamp.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/MavenModelMerger.java
rename maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSuperPomProvider.java => maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/ModelData.java (51%)
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/ModelProblemUtils.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/ProfileActivationFilePathInterpolator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/Result.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/ArtifactDescriptorReaderDelegate.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/ArtifactDescriptorUtils.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/DefaultArtifactDescriptorReader.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/DefaultModelCache.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/DefaultModelCacheFactory.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/DefaultModelResolver.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/DefaultVersionRangeResolver.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/DefaultVersionResolver.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/DefaultVersionSchemeProvider.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadata.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadataGenerator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/MavenArtifactRelocationSource.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/MavenMetadata.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/MavenSessionBuilderSupplier.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/MavenSnapshotMetadata.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/MavenWorkspaceReader.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/ModelCacheFactory.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/PluginsMetadata.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/PluginsMetadataGenerator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/PluginsMetadataGeneratorFactory.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/RelocatedArtifact.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/RemoteSnapshotMetadata.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/RemoteSnapshotMetadataGenerator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/SnapshotMetadataGeneratorFactory.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/UnresolvableModelException.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/VersionsMetadata.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/VersionsMetadataGenerator.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/VersionsMetadataGeneratorFactory.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/artifact/FatArtifactTraverser.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/artifact/MavenArtifactProperties.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/package-info.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/relocation/DistributionManagementArtifactRelocationSource.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/relocation/UserPropertiesArtifactRelocationSource.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/scopes/MavenDependencyContextRefiner.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/scopes/MavenDependencyScopes.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/scopes/MavenScopeDeriver.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/scopes/MavenScopeSelector.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/scopes/MavenSystemScopeHandler.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/type/DefaultType.java
create mode 100644 maven-api-impl/src/main/java/org/apache/maven/internal/impl/resolver/type/DefaultTypeProvider.java
create mode 100644 maven-api-impl/src/main/resources/org/apache/maven/model/pom-4.0.0.xml
create mode 100644 maven-api-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml
rename {maven-model-builder/src/test/java/org/apache/maven/model/merge => maven-api-impl/src/test/java/org/apache/maven/internal/impl/model}/MavenModelMergerTest.java (98%)
create mode 100644 maven-api-impl/src/test/java/org/apache/maven/internal/impl/standalone/DiTest.java
create mode 100644 maven-api-impl/src/test/java/org/apache/maven/internal/impl/standalone/RepositorySystemSupplier.java
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java
new file mode 100644
index 000000000000..0bfa5d9508c5
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api.services;
+
+import java.util.List;
+
+import org.apache.maven.api.Service;
+
+public interface ModelBuilder extends Service {
+
+ List VALID_MODEL_VERSIONS = List.of("4.0.0", "4.1.0");
+
+ ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilderException;
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderException.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderException.java
new file mode 100644
index 000000000000..4a0177d98c2a
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderException.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api.services;
+
+import org.apache.maven.api.annotations.Experimental;
+
+/**
+ * The Exception class throw by the {@link ProjectBuilder} service.
+ *
+ * @since 4.0.0
+ */
+@Experimental
+public class ModelBuilderException extends MavenException {
+
+ private final ModelBuilderResult result;
+
+ /**
+ * Creates a new exception from the specified interim result and its associated problems.
+ *
+ * @param result The interim result, may be {@code null}.
+ */
+ public ModelBuilderException(ModelBuilderResult result) {
+ super(result.toString());
+ this.result = result;
+ }
+
+ /**
+ * Gets the interim result of the model building up to the point where it failed.
+ *
+ * @return The interim model building result or {@code null} if not available.
+ */
+ public ModelBuilderResult getResult() {
+ return result;
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java
new file mode 100644
index 000000000000..44073903f182
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java
@@ -0,0 +1,389 @@
+/*
+ * 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.api.services;
+
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.api.Session;
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Immutable;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.annotations.NotThreadSafe;
+import org.apache.maven.api.model.Profile;
+
+import static org.apache.maven.api.services.BaseRequest.nonNull;
+
+/**
+ * Request used to build a {@link org.apache.maven.api.Project} using
+ * the {@link ProjectBuilder} service.
+ *
+ * TODO: add validationLevel, activeProfileIds, inactiveProfileIds, resolveDependencies
+ *
+ * @since 4.0.0
+ */
+@Experimental
+@Immutable
+public interface ModelBuilderRequest {
+
+ /**
+ * Denotes minimal validation of POMs. This validation level is meant for processing of POMs from repositories
+ * during metadata retrieval.
+ */
+ int VALIDATION_LEVEL_MINIMAL = 0;
+
+ /**
+ * Denotes validation as performed by Maven 2.0. This validation level is meant as a compatibility mode to allow
+ * users to migrate their projects.
+ */
+ int VALIDATION_LEVEL_MAVEN_2_0 = 20;
+
+ /**
+ * Denotes validation as performed by Maven 3.0. This validation level is meant for existing projects.
+ */
+ int VALIDATION_LEVEL_MAVEN_3_0 = 30;
+
+ /**
+ * Denotes validation as performed by Maven 3.1. This validation level is meant for existing projects.
+ */
+ int VALIDATION_LEVEL_MAVEN_3_1 = 31;
+
+ /**
+ * Denotes validation as performed by Maven 4.0. This validation level is meant for new projects.
+ */
+ int VALIDATION_LEVEL_MAVEN_4_0 = 40;
+
+ /**
+ * Denotes strict validation as recommended by the current Maven version.
+ */
+ int VALIDATION_LEVEL_STRICT = VALIDATION_LEVEL_MAVEN_4_0;
+
+ @Nonnull
+ Session getSession();
+
+ @Nonnull
+ ModelSource getSource();
+
+ int getValidationLevel();
+
+ boolean isTwoPhaseBuilding();
+
+ boolean isLocationTracking();
+
+ /**
+ * Indicates if the model to be built is a project or a dependency
+ * @return
+ */
+ boolean isProjectBuild();
+
+ boolean isProcessPlugins();
+
+ @Nonnull
+ Collection getProfiles();
+
+ @Nonnull
+ List getActiveProfileIds();
+
+ @Nonnull
+ List getInactiveProfileIds();
+
+ @Nonnull
+ Map getSystemProperties();
+
+ @Nonnull
+ Map getUserProperties();
+
+ ModelResolver getModelResolver();
+
+ ModelCache getModelCache();
+
+ @Nonnull
+ static ModelBuilderRequest build(@Nonnull ModelBuilderRequest request, @Nonnull ModelSource source) {
+ return builder(nonNull(request, "request cannot be null"))
+ .source(nonNull(source, "source cannot be null"))
+ .build();
+ }
+
+ @Nonnull
+ static ModelBuilderRequest build(@Nonnull Session session, @Nonnull ModelSource source) {
+ return builder()
+ .session(nonNull(session, "session cannot be null"))
+ .source(nonNull(source, "source cannot be null"))
+ .build();
+ }
+
+ @Nonnull
+ static ModelBuilderRequest build(@Nonnull Session session, @Nonnull Path path) {
+ return builder()
+ .session(nonNull(session, "session cannot be null"))
+ .source(ModelSource.fromPath(path))
+ .build();
+ }
+
+ @Nonnull
+ static ModelBuilderRequestBuilder builder() {
+ return new ModelBuilderRequestBuilder();
+ }
+
+ @Nonnull
+ static ModelBuilderRequestBuilder builder(ModelBuilderRequest request) {
+ return new ModelBuilderRequestBuilder(request);
+ }
+
+ @NotThreadSafe
+ class ModelBuilderRequestBuilder {
+ Session session;
+ int validationLevel;
+ boolean locationTracking;
+ boolean twoPhaseBuilding;
+ ModelSource source;
+ boolean projectBuild;
+ boolean processPlugins = true;
+ Collection profiles;
+ List activeProfileIds;
+ List inactiveProfileIds;
+ Map systemProperties;
+ Map userProperties;
+ ModelResolver modelResolver;
+ ModelCache modelCache;
+
+ ModelBuilderRequestBuilder() {}
+
+ ModelBuilderRequestBuilder(ModelBuilderRequest request) {
+ this.session = request.getSession();
+ this.validationLevel = request.getValidationLevel();
+ this.locationTracking = request.isLocationTracking();
+ this.twoPhaseBuilding = request.isTwoPhaseBuilding();
+ this.source = request.getSource();
+ this.projectBuild = request.isProjectBuild();
+ this.processPlugins = request.isProcessPlugins();
+ this.profiles = request.getProfiles();
+ this.activeProfileIds = request.getActiveProfileIds();
+ this.inactiveProfileIds = request.getInactiveProfileIds();
+ this.systemProperties = request.getSystemProperties();
+ this.userProperties = request.getUserProperties();
+ this.modelResolver = request.getModelResolver();
+ this.modelCache = request.getModelCache();
+ }
+
+ public ModelBuilderRequestBuilder session(Session session) {
+ this.session = session;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder validationLevel(int validationLevel) {
+ this.validationLevel = validationLevel;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder twoPhaseBuilding(boolean twoPhaseBuilding) {
+ this.twoPhaseBuilding = twoPhaseBuilding;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder locationTracking(boolean locationTracking) {
+ this.locationTracking = locationTracking;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder source(ModelSource source) {
+ this.source = source;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder projectBuild(boolean projectBuild) {
+ this.projectBuild = projectBuild;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder processPlugins(boolean processPlugins) {
+ this.processPlugins = processPlugins;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder profiles(List profiles) {
+ this.profiles = profiles;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder activeProfileIds(List activeProfileIds) {
+ this.activeProfileIds = activeProfileIds;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder inactiveProfileIds(List inactiveProfileIds) {
+ this.inactiveProfileIds = inactiveProfileIds;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder systemProperties(Map systemProperties) {
+ this.systemProperties = systemProperties;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder userProperties(Map userProperties) {
+ this.userProperties = userProperties;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder modelResolver(ModelResolver modelResolver) {
+ this.modelResolver = modelResolver;
+ return this;
+ }
+
+ public ModelBuilderRequestBuilder modelCache(ModelCache modelCache) {
+ this.modelCache = modelCache;
+ return this;
+ }
+
+ public ModelBuilderRequest build() {
+ return new DefaultModelBuilderRequest(
+ session,
+ validationLevel,
+ locationTracking,
+ twoPhaseBuilding,
+ source,
+ projectBuild,
+ processPlugins,
+ profiles,
+ activeProfileIds,
+ inactiveProfileIds,
+ systemProperties,
+ userProperties,
+ modelResolver,
+ modelCache);
+ }
+
+ private static class DefaultModelBuilderRequest extends BaseRequest implements ModelBuilderRequest {
+ private final int validationLevel;
+ private final boolean locationTracking;
+ private final boolean twoPhaseBuilding;
+ private final ModelSource source;
+ private final boolean projectBuild;
+ private final boolean processPlugins;
+ private final Collection profiles;
+ private final List activeProfileIds;
+ private final List inactiveProfileIds;
+ private final Map systemProperties;
+ private final Map userProperties;
+ private final ModelResolver modelResolver;
+ private final ModelCache modelCache;
+
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ DefaultModelBuilderRequest(
+ @Nonnull Session session,
+ int validationLevel,
+ boolean locationTracking,
+ boolean twoPhaseBuilding,
+ @Nonnull ModelSource source,
+ boolean projectBuild,
+ boolean processPlugins,
+ Collection profiles,
+ List activeProfileIds,
+ List inactiveProfileIds,
+ Map systemProperties,
+ Map userProperties,
+ ModelResolver modelResolver,
+ ModelCache modelCache) {
+ super(session);
+ this.validationLevel = validationLevel;
+ this.locationTracking = locationTracking;
+ this.twoPhaseBuilding = twoPhaseBuilding;
+ this.source = source;
+ this.projectBuild = projectBuild;
+ this.processPlugins = processPlugins;
+ this.profiles = profiles != null ? List.copyOf(profiles) : List.of();
+ this.activeProfileIds = activeProfileIds != null ? List.copyOf(activeProfileIds) : List.of();
+ this.inactiveProfileIds = inactiveProfileIds != null ? List.copyOf(inactiveProfileIds) : List.of();
+ this.systemProperties =
+ systemProperties != null ? Map.copyOf(systemProperties) : session.getSystemProperties();
+ this.userProperties = userProperties != null ? Map.copyOf(userProperties) : session.getUserProperties();
+ this.modelResolver = modelResolver;
+ this.modelCache = modelCache;
+ }
+
+ @Override
+ public int getValidationLevel() {
+ return validationLevel;
+ }
+
+ @Override
+ public boolean isTwoPhaseBuilding() {
+ return twoPhaseBuilding;
+ }
+
+ @Override
+ public boolean isLocationTracking() {
+ return locationTracking;
+ }
+
+ @Nonnull
+ @Override
+ public ModelSource getSource() {
+ return source;
+ }
+
+ public boolean isProjectBuild() {
+ return projectBuild;
+ }
+
+ @Override
+ public boolean isProcessPlugins() {
+ return processPlugins;
+ }
+
+ @Override
+ public Collection getProfiles() {
+ return profiles;
+ }
+
+ @Override
+ public List getActiveProfileIds() {
+ return activeProfileIds;
+ }
+
+ @Override
+ public List getInactiveProfileIds() {
+ return inactiveProfileIds;
+ }
+
+ @Override
+ public Map getSystemProperties() {
+ return systemProperties;
+ }
+
+ @Override
+ public Map getUserProperties() {
+ return userProperties;
+ }
+
+ @Override
+ public ModelResolver getModelResolver() {
+ return modelResolver;
+ }
+
+ @Override
+ public ModelCache getModelCache() {
+ return modelCache;
+ }
+ }
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java
new file mode 100644
index 000000000000..f4b8cb436e97
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java
@@ -0,0 +1,119 @@
+/*
+ * 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.api.services;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Profile;
+
+/**
+ * Result of a project build call.
+ *
+ * @since 4.0.0
+ */
+@Experimental
+public interface ModelBuilderResult {
+
+ /**
+ * Gets the sequence of model identifiers that denote the lineage of models from which the effective model was
+ * constructed. Model identifiers have the form {@code ::}. The first identifier from
+ * the list denotes the model on which the model builder was originally invoked. The last identifier will always be
+ * an empty string that by definition denotes the super POM.
+ *
+ * @return The model identifiers from the lineage of models, never {@code null}.
+ */
+ @Nonnull
+ List getModelIds();
+
+ /**
+ * Gets the file model.
+ *
+ * @return the file model, never {@code null}.
+ */
+ @Nonnull
+ Model getFileModel();
+
+ /**
+ * Gets the assembled model.
+ *
+ * @return The assembled model, never {@code null}.
+ */
+ @Nonnull
+ Model getEffectiveModel();
+
+ /**
+ * Gets the raw model as it was read from the input model source. Apart from basic validation, the raw model has not
+ * undergone any updates by the model builder, e.g. reflects neither inheritance nor interpolation.
+ *
+ * @return The raw model, never {@code null}.
+ */
+ @Nonnull
+ Model getRawModel();
+
+ /**
+ * Gets the specified raw model as it was read from a model source. Apart from basic validation, a raw model has not
+ * undergone any updates by the model builder, e.g. reflects neither inheritance nor interpolation. The model
+ * identifier should be from the collection obtained by {@link #getModelIds()}. As a special case, an empty string
+ * can be used as the identifier for the super POM.
+ *
+ * @param modelId The identifier of the desired raw model, must not be {@code null}.
+ * @return The raw model or {@code null} if the specified model id does not refer to a known model.
+ */
+ @Nonnull
+ Optional getRawModel(@Nonnull String modelId);
+
+ /**
+ * Gets the profiles from the specified model that were active during model building. The model identifier should be
+ * from the collection obtained by {@link #getModelIds()}. As a special case, an empty string can be used as the
+ * identifier for the super POM.
+ *
+ * @param modelId The identifier of the model whose active profiles should be retrieved, must not be {@code null}.
+ * @return The active profiles of the model or an empty list if none or {@code null} if the specified model id does
+ * not refer to a known model.
+ */
+ @Nonnull
+ List getActivePomProfiles(@Nonnull String modelId);
+
+ /**
+ * Gets the external profiles that were active during model building. External profiles are those that were
+ * contributed by {@link ModelBuilderRequest#getProfiles()}.
+ *
+ * @return The active external profiles or an empty list if none, never {@code null}.
+ */
+ @Nonnull
+ List getActiveExternalProfiles();
+
+ /**
+ * Gets the problems that were encountered during the project building.
+ *
+ * @return the problems that were encountered during the project building, can be empty but never {@code null}
+ */
+ @Nonnull
+ Collection getProblems();
+
+ /**
+ * Creates a human readable representation of these errors.
+ */
+ String toString();
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelCache.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelCache.java
new file mode 100644
index 000000000000..b928c3c8e4c7
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelCache.java
@@ -0,0 +1,36 @@
+/*
+ * 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.api.services;
+
+import java.util.function.Supplier;
+
+/**
+ * Caches auxiliary data used during model building like already processed raw/effective models. The data in the cache
+ * is meant for exclusive consumption by the model builder and is opaque to the cache implementation. The cache key is
+ * formed by a combination of group id, artifact id, version and tag. The first three components generally refer to the
+ * identity of a model. The tag allows for further classification of the associated data on the sole discretion of the
+ * model builder.
+ *
+ */
+public interface ModelCache {
+
+ T computeIfAbsent(String groupId, String artifactId, String version, String tag, Supplier data);
+
+ T computeIfAbsent(Source path, String tag, Supplier data);
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java
new file mode 100644
index 000000000000..0e20dbee989b
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java
@@ -0,0 +1,57 @@
+/*
+ * 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.api.services;
+
+/**
+ * Describes a problem that was encountered during model building. A problem can either be an exception that was thrown
+ * or a simple string message. In addition, a problem carries a hint about its source, e.g. the POM file that exhibits
+ * the problem.
+ *
+ */
+public interface ModelProblem extends BuilderProblem {
+
+ /**
+ * Version
+ */
+ enum Version {
+ // based on ModeBuildingResult.validationLevel
+ BASE,
+ V20,
+ V30,
+ V31,
+ V40
+ }
+
+ /**
+ * Gets the identifier of the model from which the problem originated. While the general form of this identifier is
+ * groupId:artifactId:version the returned identifier need not be complete. The identifier is derived
+ * from the information that is available at the point the problem occurs and as such merely serves as a best effort
+ * to provide information to the user to track the problem back to its origin.
+ *
+ * @return The identifier of the model from which the problem originated or an empty string if unknown, never
+ * {@code null}.
+ */
+ String getModelId();
+
+ /**
+ * Gets the applicable maven version/validation level of this problem
+ * @return The version, never {@code null}.
+ */
+ Version getVersion();
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelResolver.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelResolver.java
new file mode 100644
index 000000000000..abce25ff00e0
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelResolver.java
@@ -0,0 +1,124 @@
+/*
+ * 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.api.services;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.maven.api.Session;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.Parent;
+import org.apache.maven.api.model.Repository;
+
+/**
+ * Resolves a POM from its coordinates. During the build process, the
+ * {@link org.apache.maven.api.services.ModelBuilder} will add any relevant repositories to the model resolver. In
+ * other words, the model resolver is stateful and should not be reused across multiple model building requests.
+ *
+ */
+public interface ModelResolver {
+
+ /**
+ * Tries to resolve the POM for the specified coordinates.
+ *
+ * @param groupId The group identifier of the POM, must not be {@code null}.
+ * @param artifactId The artifact identifier of the POM, must not be {@code null}.
+ * @param version The version of the POM, must not be {@code null}.
+ * @return The source of the requested POM, never {@code null}.
+ * @throws ModelBuilderException If the POM could not be resolved from any configured repository.
+ */
+ @Nonnull
+ ModelSource resolveModel(
+ @Nonnull Session session, @Nonnull String groupId, @Nonnull String artifactId, @Nonnull String version)
+ throws ModelBuilderException;
+
+ /**
+ * Tries to resolve the POM for the specified parent coordinates possibly updating {@code parent}.
+ *
+ * Unlike the {@link #resolveModel(Session, String, String, String)} method, this method
+ * supports version ranges and updates the given {@code parent} instance to match the returned {@code ModelSource}.
+ * If {@code parent} declares a version range, the version corresponding to the returned {@code ModelSource} will
+ * be set on the given {@code parent}.
+ *
+ *
+ * @param parent The parent coordinates to resolve, must not be {@code null}.
+ *
+ * @return The source of the requested POM, never {@code null}.
+ *
+ * @throws ModelBuilderException If the POM could not be resolved from any configured repository.
+ */
+ @Nonnull
+ ModelSource resolveModel(
+ @Nonnull Session session, @Nonnull Parent parent, @Nonnull AtomicReference modified)
+ throws ModelBuilderException;
+
+ /**
+ * Tries to resolve the POM for the specified dependency coordinates possibly updating {@code dependency}.
+ *
+ * Unlike the {@link #resolveModel(Session, String, String, String)} method, this method
+ * supports version ranges and updates the given {@code dependency} instance to match the returned
+ * {@code ModelSource}. If {@code dependency} declares a version range, the version corresponding to the returned
+ * {@code ModelSource} will be set on the given {@code dependency}.
+ *
+ *
+ * @param dependency The dependency coordinates to resolve, must not be {@code null}.
+ *
+ * @return The source of the requested POM, never {@code null}.
+ *
+ * @throws ModelBuilderException If the POM could not be resolved from any configured repository.
+ *
+ * @see Dependency#clone()
+ */
+ @Nonnull
+ ModelSource resolveModel(
+ @Nonnull Session session, @Nonnull Dependency dependency, @Nonnull AtomicReference modified)
+ throws ModelBuilderException;
+
+ /**
+ * Adds a repository to use for subsequent resolution requests. The order in which repositories are added matters,
+ * repositories that were added first should also be searched first. When multiple repositories with the same
+ * identifier are added, only the first repository being added will be used.
+ *
+ * @param repository The repository to add to the internal search chain, must not be {@code null}.
+ * @throws ModelBuilderException If the repository could not be added (e.g. due to invalid URL or layout).
+ */
+ void addRepository(@Nonnull Session session, Repository repository) throws ModelBuilderException;
+
+ /**
+ * Adds a repository to use for subsequent resolution requests. The order in which repositories are added matters,
+ * repositories that were added first should also be searched first. When multiple repositories with the same
+ * identifier are added, then the value of the replace argument determines the behaviour.
+ *
+ * If replace is false then any existing repository with the same Id will remain in use. If replace
+ * is true the new repository replaces the original.
+ *
+ * @param repository The repository to add to the internal search chain, must not be {@code null}.
+ * @throws ModelBuilderException If the repository could not be added (e.g. due to invalid URL or layout).
+ */
+ void addRepository(@Nonnull Session session, Repository repository, boolean replace) throws ModelBuilderException;
+
+ /**
+ * Clones this resolver for usage in a forked resolution process. In general, implementors need not provide a deep
+ * clone. The only requirement is that invocations of {@link #addRepository(Session, Repository)} on the clone do not affect
+ * the state of the original resolver and vice versa.
+ *
+ * @return The cloned resolver, never {@code null}.
+ */
+ ModelResolver newCopy();
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelSource.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelSource.java
new file mode 100644
index 000000000000..fddf7ca86536
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelSource.java
@@ -0,0 +1,50 @@
+/*
+ * 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.api.services;
+
+import java.nio.file.Path;
+
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.annotations.Nullable;
+
+import static org.apache.maven.api.services.BaseRequest.nonNull;
+
+public interface ModelSource extends Source {
+
+ interface ModelLocator {
+ /**
+ * Returns the file containing the pom or null if a pom can not be found at the given file or in the given directory.
+ *
+ * @since 4.0.0
+ */
+ Path locateExistingPom(Path project);
+ }
+
+ ModelSource resolve(ModelLocator modelLocator, String relative);
+
+ @Nonnull
+ static ModelSource fromPath(@Nonnull Path path) {
+ return fromPath(path, null);
+ }
+
+ @Nonnull
+ static ModelSource fromPath(@Nonnull Path path, @Nullable String location) {
+ return new PathModelSource(nonNull(path, "path cannot be null"), location);
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathModelSource.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathModelSource.java
new file mode 100644
index 000000000000..44438dda0204
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathModelSource.java
@@ -0,0 +1,40 @@
+/*
+ * 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.api.services;
+
+import java.io.File;
+import java.nio.file.Path;
+
+class PathModelSource extends PathSource implements ModelSource {
+
+ PathModelSource(Path path, String location) {
+ super(path, location);
+ }
+
+ @Override
+ public ModelSource resolve(ModelLocator locator, String relative) {
+ String norm = relative.replace('\\', File.separatorChar).replace('/', File.separatorChar);
+ Path path = getPath().getParent().resolve(norm);
+ Path relatedPom = locator.locateExistingPom(path);
+ if (relatedPom != null) {
+ return new PathModelSource(relatedPom.normalize(), null);
+ }
+ return null;
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathSource.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathSource.java
index a11459494786..51520406537d 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathSource.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathSource.java
@@ -26,9 +26,15 @@
class PathSource implements Source {
private final Path path;
+ private final String location;
PathSource(Path path) {
+ this(path, null);
+ }
+
+ PathSource(Path path, String location) {
this.path = path;
+ this.location = location != null ? location : path.toString();
}
@Override
@@ -43,7 +49,7 @@ public InputStream openStream() throws IOException {
@Override
public String getLocation() {
- return path.toString();
+ return location;
}
@Override
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java
index ca4c40d48a19..8c2c872a1787 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java
@@ -39,5 +39,8 @@ public interface TypeRegistry extends ExtensibleEnumRegistry {
* @return the type
*/
@Nonnull
- Type require(@Nonnull String id);
+ @Override
+ default Type require(@Nonnull String id) {
+ return lookup(id).orElseThrow(() -> new IllegalArgumentException("Unknown extensible enum value '" + id + "'"));
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParser.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParser.java
index c9e2efc486a6..bd1025d1ae91 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParser.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParser.java
@@ -39,6 +39,11 @@
@Consumer
public interface ModelParser extends SpiService {
+ /**
+ * Option that can be specified in the options map. The value should be a Boolean.
+ */
+ String STRICT = "strict";
+
/**
* Locates the pom in the given directory.
*
diff --git a/maven-api-impl/pom.xml b/maven-api-impl/pom.xml
index fe6a104bcc1f..ef015a1a2904 100644
--- a/maven-api-impl/pom.xml
+++ b/maven-api-impl/pom.xml
@@ -34,10 +34,18 @@ under the License.
org.apache.mavenmaven-api-core
+
+ org.apache.maven
+ maven-api-spi
+ org.apache.mavenmaven-di
+
+ org.apache.maven
+ maven-repository-metadata
+ org.apache.maven.resolvermaven-resolver-api
@@ -52,12 +60,11 @@ under the License.
org.apache.maven.resolver
- maven-resolver-supplier
- ${resolverVersion}
+ maven-resolver-impl
- org.apache.maven
- maven-resolver-provider
+ org.codehaus.plexus
+ plexus-interpolation
@@ -65,6 +72,21 @@ under the License.
mockito-junit-jupitertest
+
+ org.apache.maven.resolver
+ maven-resolver-connector-basic
+ test
+
+
+ org.apache.maven.resolver
+ maven-resolver-transport-file
+ test
+
+
+ org.apache.maven.resolver
+ maven-resolver-transport-apache
+ test
+
@@ -128,6 +150,36 @@ under the License.
+
+ model-v4
+
+ velocity
+
+ generate-sources
+
+ 4.1.0
+ ${project.basedir}/../api/maven-api-model
+ ${project.basedir}/../src/mdo
+
+ src/main/mdo/maven.mdo
+
+
+ merger.vm
+ transformer.vm
+ reader-stax.vm
+ writer-stax.vm
+ model-version.vm
+
+
+ forcedIOModelVersion=4.0.0
+ packageModelV3=org.apache.maven.model
+ packageModelV4=org.apache.maven.api.model
+ packageToolV4=org.apache.maven.model.v4
+ isMavenModel=true
+ minimalVersion=4.0.0
+
+
+ modello-site-docsnone
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
new file mode 100644
index 000000000000..3f253dd58bca
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.api.services.model;
+
+import java.util.List;
+
+import org.apache.maven.api.model.DependencyManagement;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles the import of dependency management from other models into the target model.
+ *
+ */
+public interface DependencyManagementImporter {
+
+ /**
+ * Imports the specified dependency management sections into the given target model.
+ *
+ * @param target The model into which to import the dependency management section, must not be null.
+ * @param sources The dependency management sections to import, may be null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model importManagement(
+ Model target,
+ List extends DependencyManagement> sources,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
new file mode 100644
index 000000000000..0359bec97034
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
@@ -0,0 +1,39 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles injection of dependency management into the model.
+ *
+ */
+public interface DependencyManagementInjector {
+
+ /**
+ * Merges default values from the dependency management section of the given model into itself.
+ *
+ * @param model The model into which to merge the values specified by its dependency management sections, must not
+ * be null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
new file mode 100644
index 000000000000..4e2814df205a
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles inheritance of model values.
+ *
+ */
+public interface InheritanceAssembler {
+
+ /**
+ * Merges values from the specified parent model into the given child model. Implementations are expected to keep
+ * parent and child completely decoupled by injecting deep copies of objects into the child rather than the original
+ * objects from the parent.
+ *
+ * @param child The child model into which to merge the values inherited from the parent, must not be
+ * null.
+ * @param parent The (read-only) parent model from which to inherit the values, may be null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model assembleModelInheritance(
+ Model child, Model parent, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/LifecycleBindingsInjector.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/LifecycleBindingsInjector.java
new file mode 100644
index 000000000000..c3ebf10ba9b8
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/LifecycleBindingsInjector.java
@@ -0,0 +1,41 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles injection of plugin executions induced by the lifecycle bindings for a packaging.
+ *
+ */
+public interface LifecycleBindingsInjector {
+
+ /**
+ * Injects plugin executions induced by lifecycle bindings into the specified model. The model has already undergone
+ * injection of plugin management so any plugins that are injected by lifecycle bindings and are not already present
+ * in the model's plugin section need to be subjected to the model's plugin management.
+ *
+ * @param model The model into which to inject the default plugin executions for its packaging, must not be
+ * null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model injectLifecycleBindings(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingEvent.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingEvent.java
new file mode 100644
index 000000000000..22328349b764
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingEvent.java
@@ -0,0 +1,50 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Holds data relevant for a model building event.
+ *
+ */
+public interface ModelBuildingEvent {
+
+ /**
+ * Gets the model being built. The precise state of this model depends on the event being fired.
+ *
+ * @return The model being built, never {@code null}.
+ */
+ Model model();
+
+ /**
+ * Gets the model building request being processed.
+ *
+ * @return The model building request being processed, never {@code null}.
+ */
+ ModelBuilderRequest request();
+
+ /**
+ * Gets the container used to collect problems that were encountered while processing the event.
+ *
+ * @return The container used to collect problems that were encountered, never {@code null}.
+ */
+ ModelProblemCollector problems();
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingListener.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingListener.java
new file mode 100644
index 000000000000..a0c2bfb4a768
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelBuildingListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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.api.services.model;
+
+/**
+ * Defines events that the model builder fires during construction of the effective model. When a listener encounters
+ * errors while processing the event, it can report these problems via {@link ModelBuildingEvent#problems()}.
+ */
+public interface ModelBuildingListener {
+
+ /**
+ * Notifies the listener that the model has been constructed to the extent where build extensions can be processed.
+ *
+ * @param event The details about the event.
+ */
+ default void buildExtensionsAssembled(ModelBuildingEvent event) {}
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
new file mode 100644
index 000000000000..cf93c2e1df38
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
@@ -0,0 +1,47 @@
+/*
+ * 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.api.services.model;
+
+import java.nio.file.Path;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Replaces expressions of the form ${token} with their effective values. Effective values are basically
+ * calculated from the elements of the model itself and the execution properties from the building request.
+ *
+ */
+public interface ModelInterpolator {
+
+ /**
+ * Interpolates expressions in the specified model. Note that implementations are free to either interpolate the
+ * provided model directly or to create a clone of the model and interpolate the clone. Callers should always use
+ * the returned model and must not rely on the input model being updated.
+ *
+ * @param model The model to interpolate, must not be {@code null}.
+ * @param projectDir The project directory, may be {@code null} if the model does not belong to a local project but
+ * to some artifact's metadata.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ * @return The interpolated model, never {@code null}.
+ * @since 4.0.0
+ */
+ Model interpolateModel(Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
new file mode 100644
index 000000000000..a31728975718
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles normalization of a model. In this context, normalization is the process of producing a canonical
+ * representation for models that physically look different but are semantically equivalent.
+ *
+ */
+public interface ModelNormalizer {
+
+ /**
+ * Merges duplicate elements like multiple declarations of the same build plugin in the specified model.
+ *
+ * @param model The model whose duplicate elements should be merged, must not be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model mergeDuplicates(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Sets default values in the specified model that for technical reasons cannot be set directly in the Modello
+ * definition.
+ *
+ * @param model The model in which to set the default values, must not be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model injectDefaultValues(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
new file mode 100644
index 000000000000..07a36b95498c
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.api.services.model;
+
+import java.nio.file.Path;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Resolves relative paths of a model against a specific base directory.
+ *
+ */
+public interface ModelPathTranslator {
+
+ /**
+ * Resolves the well-known paths of the specified model against the given base directory. Paths within plugin
+ * configuration are not processed.
+ *
+ * @param model The model whose paths should be resolved, may be {@code null}.
+ * @param basedir The base directory to resolve relative paths against, may be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @since 4.0.0
+ */
+ Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest request);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProblemCollector.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProblemCollector.java
new file mode 100644
index 000000000000..3e89b7192fba
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProblemCollector.java
@@ -0,0 +1,65 @@
+/*
+ * 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.api.services.model;
+
+import java.util.List;
+
+import org.apache.maven.api.model.InputLocation;
+import org.apache.maven.api.services.BuilderProblem;
+import org.apache.maven.api.services.ModelProblem;
+
+/**
+ * Collects problems that are encountered during model building. The primary purpose of this component is to account for
+ * the fact that the problem reporter has/should not have information about the calling context and hence cannot provide
+ * an expressive source hint for the model problem. Instead, the source hint is configured by the model builder before
+ * it delegates to other components that potentially encounter problems. Then, the problem reporter can focus on
+ * providing a simple error message, leaving the donkey work of creating a nice model problem to this component.
+ *
+ */
+public interface ModelProblemCollector {
+
+ /**
+ * The collected problems.
+ * @return a list of model problems encountered, never {@code null}
+ */
+ List getProblems();
+
+ default void add(BuilderProblem.Severity severity, ModelProblem.Version version, String message) {
+ add(severity, version, message, null, null);
+ }
+
+ default void add(
+ BuilderProblem.Severity severity, ModelProblem.Version version, String message, InputLocation location) {
+ add(severity, version, message, location, null);
+ }
+
+ default void add(
+ BuilderProblem.Severity severity, ModelProblem.Version version, String message, Exception exception) {
+ add(severity, version, message, null, exception);
+ }
+
+ void add(
+ BuilderProblem.Severity severity,
+ ModelProblem.Version version,
+ String message,
+ InputLocation location,
+ Exception exception);
+
+ void add(ModelProblem problem);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProcessor.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProcessor.java
new file mode 100644
index 000000000000..0d5219cecbad
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelProcessor.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api.services.model;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.apache.maven.api.annotations.Nullable;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.xml.XmlReaderException;
+import org.apache.maven.api.services.xml.XmlReaderRequest;
+
+/**
+ * ModelProcessor
+ */
+public interface ModelProcessor {
+
+ /**
+ * Returns the file containing the pom to be parsed or null if a pom can not be found
+ * at the given file or in the given directory.
+ */
+ @Nullable
+ Path locateExistingPom(Path project);
+
+ /**
+ * Reads the model from the specified byte stream. The stream will be automatically closed before the method
+ * returns.
+ *
+ * @param request The reader request to deserialize the model, must not be {@code null}.
+ * @return The deserialized model, never {@code null}.
+ * @throws IOException If the model could not be deserialized.
+ * @throws XmlReaderException If the input format could not be parsed.
+ */
+ Model read(XmlReaderRequest request) throws IOException, XmlReaderException;
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelSourceTransformer.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelSourceTransformer.java
new file mode 100644
index 000000000000..e4c42af3e8fd
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelSourceTransformer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.api.services.model;
+
+import java.nio.file.Path;
+
+import org.apache.maven.api.model.Model;
+
+/**
+ * The ModelSourceTransformer is a way to transform the local pom while streaming the input.
+ *
+ * The {@link #transform(Path, TransformerContext, Model)} method uses a Path on purpose, to ensure the
+ * local pom is the original source.
+ *
+ * @since 4.0.0
+ */
+public interface ModelSourceTransformer {
+ /**
+ *
+ * @param pomFile the pom file, cannot be null
+ * @param context the context, cannot be null
+ * @param model the model to transform
+ * @throws TransformerException if the transformation fails
+ */
+ Model transform(Path pomFile, TransformerContext context, Model model) throws TransformerException;
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
new file mode 100644
index 000000000000..221e233d8d3f
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Normalizes URLs to remove the ugly parent references "../" that got potentially inserted by URL adjustment during
+ * model inheritance.
+ *
+ */
+public interface ModelUrlNormalizer {
+
+ /**
+ * Normalizes the well-known URLs of the specified model.
+ *
+ * @param model The model whose URLs should be normalized, may be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ */
+ Model normalize(Model model, ModelBuilderRequest request);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java
new file mode 100644
index 000000000000..717b0dfeb329
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Checks the model for missing or invalid values.
+ *
+ */
+public interface ModelValidator {
+ /**
+ * Checks the specified file model for missing or invalid values. This model is directly created from the POM
+ * file and has not been subjected to inheritance, interpolation or profile/default injection.
+ *
+ * @param model The model to validate, must not be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ default void validateFileModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
+ // do nothing
+ }
+
+ /**
+ * Checks the specified (raw) model for missing or invalid values. The raw model is the file model + buildpom filter
+ * transformation and has not been subjected to inheritance, interpolation or profile/default injection.
+ *
+ * @param model The model to validate, must not be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ void validateRawModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Checks the specified (effective) model for missing or invalid values. The effective model is fully assembled and
+ * has undergone inheritance, interpolation and other model operations.
+ *
+ * @param model The model to validate, must not be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ void validateEffectiveModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionParser.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionParser.java
new file mode 100644
index 000000000000..c14b34e93a9a
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionParser.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.api.services.model;
+
+import org.apache.maven.api.Version;
+import org.apache.maven.api.VersionConstraint;
+import org.apache.maven.api.VersionRange;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.services.VersionParserException;
+
+/**
+ * Model builder specific version parser. It is intentionally not
+ * {@link org.apache.maven.api.services.VersionParser} as this is not a service,
+ * but at Maven runtime it MAY actually use that service.
+ *
+ * @since 4.0.0
+ */
+public interface ModelVersionParser {
+
+ /**
+ * Parses the specified version string, for example "1.0".
+ *
+ * @param version the version string to parse, must not be {@code null}
+ * @return the parsed version, never {@code null}
+ * @throws VersionParserException if the string violates the syntax rules of this scheme
+ */
+ @Nonnull
+ Version parseVersion(@Nonnull String version);
+
+ /**
+ * Parses the specified version range specification, for example "[1.0,2.0)".
+ *
+ * @param range the range specification to parse, must not be {@code null}
+ * @return the parsed version range, never {@code null}
+ * @throws VersionParserException if the range specification violates the syntax rules of this scheme
+ */
+ @Nonnull
+ VersionRange parseVersionRange(@Nonnull String range);
+
+ /**
+ * Parses the specified version constraint specification, for example "1.0" or "[1.0,2.0)".
+ *
+ * @param constraint the range specification to parse, must not be {@code null}
+ * @return the parsed version constraint, never {@code null}
+ * @throws VersionParserException if the range specification violates the syntax rules of this scheme
+ */
+ @Nonnull
+ VersionConstraint parseVersionConstraint(@Nonnull String constraint);
+
+ /**
+ * Checks whether a given artifact version is considered a {@code SNAPSHOT} or not.
+ */
+ boolean isSnapshot(@Nonnull String version);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionProcessor.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionProcessor.java
new file mode 100644
index 000000000000..8867441f098e
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ModelVersionProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.api.services.model;
+
+import java.util.Properties;
+
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Allows a fixed set of properties that are valid inside a version and that could be overwritten for example on the
+ * commandline
+ */
+public interface ModelVersionProcessor {
+
+ /**
+ * @param property the property to check
+ * @return true if this is a valid property for this processor
+ */
+ boolean isValidProperty(String property);
+
+ /**
+ * This method is responsible for examining the request and possibly overwrite of the valid properties in the model
+ *
+ * @param modelProperties
+ * @param request
+ */
+ void overwriteModelProperties(Properties modelProperties, ModelBuilderRequest request);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PathTranslator.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PathTranslator.java
new file mode 100644
index 000000000000..8f1fec0b5762
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PathTranslator.java
@@ -0,0 +1,40 @@
+/*
+ * 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.api.services.model;
+
+import java.nio.file.Path;
+
+/**
+ * Resolves relative paths against a specific base directory.
+ *
+ */
+public interface PathTranslator {
+
+ /**
+ * Resolves the specified path against the given base directory. The resolved path will be absolute and uses the
+ * platform-specific file separator if a base directory is given. Otherwise, the input path will be returned
+ * unaltered.
+ *
+ * @param path The path to resolve, may be {@code null}.
+ * @param basedir The base directory to resolve relative paths against, may be {@code null}.
+ * @return The resolved path or {@code null} if the input path was {@code null}.
+ * @since 4.0.0
+ */
+ String alignToBaseDirectory(String path, Path basedir);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
new file mode 100644
index 000000000000..1e863a391c22
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
@@ -0,0 +1,38 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles expansion of general build plugin configuration into individual executions.
+ *
+ */
+public interface PluginConfigurationExpander {
+
+ /**
+ * Merges values from general build plugin configuration into the individual plugin executions of the given model.
+ *
+ * @param model The model whose build plugin configuration should be expanded, must not be null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
new file mode 100644
index 000000000000..f78e7bb04e75
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
@@ -0,0 +1,39 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles injection of plugin management into the model.
+ *
+ */
+public interface PluginManagementInjector {
+
+ /**
+ * Merges default values from the plugin management section of the given model into itself.
+ *
+ * @param model The model into which to merge the values specified by its plugin management section, must not be
+ * null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java
new file mode 100644
index 000000000000..8c1750342b5b
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java
@@ -0,0 +1,81 @@
+/*
+ * 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.api.services.model;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Describes the environmental context used to determine the activation status of profiles.
+ *
+ */
+public interface ProfileActivationContext {
+ /**
+ * Key of the property containing the project's packaging.
+ * Available in {@link #getUserProperties()}.
+ * @since 4.0.0
+ */
+ String PROPERTY_NAME_PACKAGING = "packaging";
+
+ /**
+ * Gets the identifiers of those profiles that should be activated by explicit demand.
+ *
+ * @return The identifiers of those profiles to activate, never {@code null}.
+ */
+ List getActiveProfileIds();
+
+ /**
+ * Gets the identifiers of those profiles that should be deactivated by explicit demand.
+ *
+ * @return The identifiers of those profiles to deactivate, never {@code null}.
+ */
+ List getInactiveProfileIds();
+
+ /**
+ * Gets the system properties to use for interpolation and profile activation. The system properties are collected
+ * from the runtime environment like {@link System#getProperties()} and environment variables.
+ *
+ * @return The execution properties, never {@code null}.
+ */
+ Map getSystemProperties();
+
+ /**
+ * Gets the user properties to use for interpolation and profile activation. The user properties have been
+ * configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command
+ * line.
+ *
+ * @return The user properties, never {@code null}.
+ */
+ Map getUserProperties();
+
+ /**
+ * Gets the base directory of the current project (if any).
+ *
+ * @return The base directory of the current project or {@code null} if none.
+ */
+ Path getProjectDirectory();
+
+ /**
+ * Gets current calculated project properties
+ *
+ * @return The project properties, never {@code null}.
+ */
+ Map getProjectProperties();
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivator.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivator.java
new file mode 100644
index 000000000000..954f3a5089ab
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Profile;
+
+/**
+ * Determines whether a profile should be activated.
+ *
+ */
+public interface ProfileActivator {
+
+ /**
+ * Determines whether the specified profile is active in the given activator context.
+ *
+ * @param profile The profile whose activation status should be determined, must not be {@code null}.
+ * @param context The environmental context used to determine the activation status of the profile, must not be
+ * {@code null}.
+ * @param problems The container used to collect problems (e.g. bad syntax) that were encountered, must not be
+ * {@code null}.
+ * @return {@code true} if the profile is active, {@code false} otherwise.
+ */
+ boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems);
+
+ /**
+ * Determines whether specified activation method is present in configuration or not. It should help to have AND
+ * between activation conditions
+ * Need for solving https://issues.apache.org/jira/browse/MNG-4565
+ * @param profile The profile whose activation status should be determined, must not be {@code null}.
+ * @param context The environmental context used to determine the activation status of the profile, must not be
+ * {@code null}.
+ * @param problems The container used to collect problems (e.g. bad syntax) that were encountered, must not be
+ * {@code null}.
+ * @return {@code true} if the profile is active, {@code false} otherwise.
+ */
+ boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
new file mode 100644
index 000000000000..82457b01ab66
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
@@ -0,0 +1,58 @@
+/*
+ * 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.api.services.model;
+
+import java.util.List;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Profile;
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * Handles profile injection into the model.
+ *
+ */
+public interface ProfileInjector {
+
+ /**
+ * Merges values from the specified profile into the given model. Implementations are expected to keep the profile
+ * and model completely decoupled by injecting deep copies rather than the original objects from the profile.
+ *
+ * @param model The model into which to merge the values defined by the profile, must not be null.
+ * @param profile The (read-only) profile whose values should be injected, may be null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ default Model injectProfile(
+ Model model, Profile profile, ModelBuilderRequest request, ModelProblemCollector problems) {
+ return injectProfiles(model, List.of(profile), request, problems);
+ }
+
+ /**
+ * Merges values from the specified profile into the given model. Implementations are expected to keep the profile
+ * and model completely decoupled by injecting deep copies rather than the original objects from the profile.
+ *
+ * @param model The model into which to merge the values defined by the profile, must not be null.
+ * @param profiles The (read-only) list of profiles whose values should be injected, must not be null.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
+ Model injectProfiles(
+ Model model, List profiles, ModelBuilderRequest request, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java
new file mode 100644
index 000000000000..370b6106c055
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java
@@ -0,0 +1,44 @@
+/*
+ * 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.api.services.model;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.maven.api.model.Profile;
+
+/**
+ * Calculates the active profiles among a given collection of profiles.
+ *
+ */
+public interface ProfileSelector {
+
+ /**
+ * Determines the profiles which are active in the specified activation context. Active profiles will eventually be
+ * injected into the model.
+ *
+ * @param profiles The profiles whose activation status should be determined, must not be {@code null}.
+ * @param context The environmental context used to determine the activation status of a profile, must not be
+ * {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ * @return The profiles that have been activated, never {@code null}.
+ */
+ List getActiveProfiles(
+ Collection profiles, ProfileActivationContext context, ModelProblemCollector problems);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/RootLocator.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/RootLocator.java
new file mode 100644
index 000000000000..505dbbe12a19
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/RootLocator.java
@@ -0,0 +1,67 @@
+/*
+ * 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.api.services.model;
+
+import java.nio.file.Path;
+
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.annotations.Nullable;
+
+/**
+ * Interface used to locate the root directory for a given project.
+ *
+ * The root locator is usually looked up from the plexus container.
+ * One notable exception is the computation of the early {@code session.rootDirectory}
+ * property which happens very early. The implementation used in this case
+ * will be discovered using the JDK service mechanism.
+ *
+ * The default implementation will look for a {@code .mvn} child directory
+ * or a {@code pom.xml} containing the {@code root="true"} attribute.
+ */
+public interface RootLocator {
+
+ String UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE = "Unable to find the root directory. "
+ + "Create a .mvn directory in the root directory or add the root=\"true\""
+ + " attribute on the root project's model to identify it.";
+
+ @Nonnull
+ default Path findMandatoryRoot(Path basedir) {
+ Path rootDirectory = findRoot(basedir);
+ if (rootDirectory == null) {
+ throw new IllegalStateException(getNoRootMessage());
+ }
+ return rootDirectory;
+ }
+
+ @Nullable
+ default Path findRoot(Path basedir) {
+ Path rootDirectory = basedir;
+ while (rootDirectory != null && !isRootDirectory(rootDirectory)) {
+ rootDirectory = rootDirectory.getParent();
+ }
+ return rootDirectory;
+ }
+
+ @Nonnull
+ default String getNoRootMessage() {
+ return UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE;
+ }
+
+ boolean isRootDirectory(Path dir);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContext.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContext.java
new file mode 100644
index 000000000000..9bfb14441de4
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContext.java
@@ -0,0 +1,67 @@
+/*
+ * 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.api.services.model;
+
+import java.nio.file.Path;
+
+import org.apache.maven.api.model.Model;
+
+/**
+ * Context used to transform a pom file.
+ *
+ *
+ * @since 4.0.0
+ */
+public interface TransformerContext {
+
+ /**
+ * Key to get the TransformerContext from the SessionData
+ */
+ Object KEY = TransformerContext.class;
+
+ /**
+ * Get the value of the Maven user property.
+ */
+ String getUserProperty(String key);
+
+ /**
+ * Get the model based on the path when resolving the parent based on relativePath.
+ *
+ * @param from the requiring model
+ * @param pomFile the path to the pomFile
+ * @return the model, otherwise {@code null}
+ */
+ Model getRawModel(Path from, Path pomFile);
+
+ /**
+ * Get the model from the reactor based on the groupId and artifactId when resolving reactor dependencies.
+ *
+ * @param from the requiring model
+ * @param groupId the groupId
+ * @param artifactId the artifactId
+ * @return the model, otherwise {@code null}
+ * @throws IllegalStateException if multiple versions of the same GA are part of the reactor
+ */
+ Model getRawModel(Path from, String groupId, String artifactId);
+
+ /**
+ * Locate the POM file inside the given directory.
+ */
+ Path locate(Path path);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContextBuilder.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContextBuilder.java
new file mode 100644
index 000000000000..ef2c954bbedc
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerContextBuilder.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.api.services.model;
+
+import org.apache.maven.api.services.ModelBuilderRequest;
+
+/**
+ * The transformerContextBuilder is responsible for initializing the TransformerContext.
+ * In case rawModels are missing, it could do new buildingRequests on the ModelBuilder.
+ *
+ * @since 4.0.0
+ */
+public interface TransformerContextBuilder {
+ /**
+ * This method is used to initialize the TransformerContext
+ *
+ * @param request the modelBuildingRequest
+ * @param problems the problemCollector
+ * @return the mutable transformerContext
+ */
+ TransformerContext initialize(ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * The immutable transformerContext, can be used after the buildplan is finished.
+ *
+ * @return the immutable transformerContext
+ */
+ TransformerContext build();
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerException.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerException.java
new file mode 100644
index 000000000000..1c1951d35ea5
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/TransformerException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.services.MavenException;
+
+/**
+ *
+ * @since 4.0.0
+ */
+public class TransformerException extends MavenException {
+
+ public TransformerException(Exception e) {
+ super(e);
+ }
+
+ public TransformerException(String message, Throwable exception) {
+ super(message, exception);
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/UrlNormalizer.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/UrlNormalizer.java
new file mode 100644
index 000000000000..29dab47f0589
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/UrlNormalizer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api.services.model;
+
+public interface UrlNormalizer {
+
+ /**
+ * Normalizes the specified URL.
+ *
+ * @param url The URL to normalize, may be {@code null}.
+ * @return The normalized URL or {@code null} if the input was {@code null}.
+ */
+ String normalize(String url);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/api/services/model/WorkspaceModelResolver.java b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/WorkspaceModelResolver.java
new file mode 100644
index 000000000000..4dd18968d567
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/api/services/model/WorkspaceModelResolver.java
@@ -0,0 +1,31 @@
+/*
+ * 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.api.services.model;
+
+import org.apache.maven.api.model.Model;
+
+/**
+ * WorkspaceModelResolver
+ */
+public interface WorkspaceModelResolver {
+
+ Model resolveRawModel(String groupId, String artifactId, String versionConstraint);
+
+ Model resolveEffectiveModel(String groupId, String artifactId, String versionConstraint);
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultArtifact.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultArtifact.java
index 563797f05c49..405d730cabbe 100644
--- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultArtifact.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultArtifact.java
@@ -24,7 +24,6 @@
import org.apache.maven.api.ArtifactCoordinate;
import org.apache.maven.api.Version;
import org.apache.maven.api.annotations.Nonnull;
-import org.apache.maven.repository.internal.DefaultModelVersionParser;
import static org.apache.maven.internal.impl.Utils.nonNull;
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java
index 8137a2e2c968..a29a0bb1659b 100644
--- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java
@@ -28,7 +28,6 @@
import org.apache.maven.api.Version;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
-import org.apache.maven.repository.internal.DefaultModelVersionParser;
import org.eclipse.aether.artifact.ArtifactProperties;
import static org.apache.maven.internal.impl.Utils.nonNull;
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelUrlNormalizer.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelUrlNormalizer.java
new file mode 100644
index 000000000000..f593ed430132
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelUrlNormalizer.java
@@ -0,0 +1,80 @@
+/*
+ * 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.internal.impl;
+
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.DistributionManagement;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Scm;
+import org.apache.maven.api.model.Site;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.model.ModelUrlNormalizer;
+import org.apache.maven.api.services.model.UrlNormalizer;
+
+/**
+ * Normalizes URLs to remove the ugly parent references "../" that got potentially inserted by URL adjustment during
+ * model inheritance.
+ *
+ */
+@Named
+@Singleton
+public class DefaultModelUrlNormalizer implements ModelUrlNormalizer {
+
+ private final UrlNormalizer urlNormalizer;
+
+ @Inject
+ public DefaultModelUrlNormalizer(UrlNormalizer urlNormalizer) {
+ this.urlNormalizer = urlNormalizer;
+ }
+
+ @Override
+ public Model normalize(Model model, ModelBuilderRequest request) {
+ if (model == null) {
+ return null;
+ }
+
+ Model.Builder builder = Model.newBuilder(model);
+ builder.url(normalize(model.getUrl()));
+
+ Scm scm = model.getScm();
+ if (scm != null) {
+ builder.scm(Scm.newBuilder(scm)
+ .url(normalize(scm.getUrl()))
+ .connection(normalize(scm.getConnection()))
+ .developerConnection(normalize(scm.getDeveloperConnection()))
+ .build());
+ }
+
+ DistributionManagement dist = model.getDistributionManagement();
+ if (dist != null) {
+ Site site = dist.getSite();
+ if (site != null) {
+ builder.distributionManagement(dist.withSite(site.withUrl(normalize(site.getUrl()))));
+ }
+ }
+
+ return builder.build();
+ }
+
+ private String normalize(String url) {
+ return urlNormalizer.normalize(url);
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelVersionParser.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelVersionParser.java
new file mode 100644
index 000000000000..4d674d183878
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultModelVersionParser.java
@@ -0,0 +1,288 @@
+/*
+ * 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.internal.impl;
+
+import java.util.regex.Pattern;
+
+import org.apache.maven.api.Version;
+import org.apache.maven.api.VersionConstraint;
+import org.apache.maven.api.VersionRange;
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.services.VersionParserException;
+import org.apache.maven.api.services.model.ModelVersionParser;
+import org.eclipse.aether.version.InvalidVersionSpecificationException;
+import org.eclipse.aether.version.VersionScheme;
+
+import static java.util.Objects.requireNonNull;
+
+@Named
+@Singleton
+public class DefaultModelVersionParser implements ModelVersionParser {
+ private static final String SNAPSHOT = "SNAPSHOT";
+ private static final Pattern SNAPSHOT_TIMESTAMP = Pattern.compile("^(.*-)?([0-9]{8}\\.[0-9]{6}-[0-9]+)$");
+ private final VersionScheme versionScheme;
+
+ @Inject
+ public DefaultModelVersionParser(VersionScheme versionScheme) {
+ this.versionScheme = requireNonNull(versionScheme, "versionScheme");
+ }
+
+ @Override
+ public Version parseVersion(String version) {
+ requireNonNull(version, "version");
+ return new DefaultVersion(versionScheme, version);
+ }
+
+ @Override
+ public VersionRange parseVersionRange(String range) {
+ requireNonNull(range, "range");
+ return new DefaultVersionRange(versionScheme, range);
+ }
+
+ @Override
+ public boolean isSnapshot(String version) {
+ return checkSnapshot(version);
+ }
+
+ public static boolean checkSnapshot(String version) {
+ return version.endsWith(SNAPSHOT) || SNAPSHOT_TIMESTAMP.matcher(version).matches();
+ }
+
+ @Override
+ public VersionConstraint parseVersionConstraint(String constraint) {
+ requireNonNull(constraint, "constraint");
+ return new DefaultVersionConstraint(versionScheme, constraint);
+ }
+
+ static class DefaultVersion implements Version {
+ private final VersionScheme versionScheme;
+ private final org.eclipse.aether.version.Version delegate;
+
+ DefaultVersion(VersionScheme versionScheme, org.eclipse.aether.version.Version delegate) {
+ this.versionScheme = versionScheme;
+ this.delegate = delegate;
+ }
+
+ DefaultVersion(VersionScheme versionScheme, String delegateValue) {
+ this.versionScheme = versionScheme;
+ try {
+ this.delegate = versionScheme.parseVersion(delegateValue);
+ } catch (InvalidVersionSpecificationException e) {
+ throw new VersionParserException("Unable to parse version: " + delegateValue, e);
+ }
+ }
+
+ @Override
+ public int compareTo(Version o) {
+ if (o instanceof DefaultVersion) {
+ return delegate.compareTo(((DefaultVersion) o).delegate);
+ } else {
+ return compareTo(new DefaultVersion(versionScheme, o.asString()));
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DefaultVersion that = (DefaultVersion) o;
+ return delegate.equals(that.delegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public String asString() {
+ return delegate.toString();
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+ }
+
+ static class DefaultVersionRange implements VersionRange {
+ private final VersionScheme versionScheme;
+ private final org.eclipse.aether.version.VersionRange delegate;
+
+ DefaultVersionRange(VersionScheme versionScheme, org.eclipse.aether.version.VersionRange delegate) {
+ this.versionScheme = versionScheme;
+ this.delegate = delegate;
+ }
+
+ DefaultVersionRange(VersionScheme versionScheme, String delegateValue) {
+ this.versionScheme = versionScheme;
+ try {
+ this.delegate = versionScheme.parseVersionRange(delegateValue);
+ } catch (InvalidVersionSpecificationException e) {
+ throw new VersionParserException("Unable to parse version range: " + delegateValue, e);
+ }
+ }
+
+ @Override
+ public boolean contains(Version version) {
+ if (version instanceof DefaultVersion) {
+ return delegate.containsVersion(((DefaultVersion) version).delegate);
+ } else {
+ return contains(new DefaultVersion(versionScheme, version.asString()));
+ }
+ }
+
+ @Override
+ public Boundary getUpperBoundary() {
+ org.eclipse.aether.version.VersionRange.Bound bound = delegate.getUpperBound();
+ if (bound == null) {
+ return null;
+ }
+ return new Boundary() {
+ @Override
+ public Version getVersion() {
+ return new DefaultVersion(versionScheme, bound.getVersion());
+ }
+
+ @Override
+ public boolean isInclusive() {
+ return bound.isInclusive();
+ }
+ };
+ }
+
+ @Override
+ public Boundary getLowerBoundary() {
+ org.eclipse.aether.version.VersionRange.Bound bound = delegate.getLowerBound();
+ if (bound == null) {
+ return null;
+ }
+ return new Boundary() {
+ @Override
+ public Version getVersion() {
+ return new DefaultVersion(versionScheme, bound.getVersion());
+ }
+
+ @Override
+ public boolean isInclusive() {
+ return bound.isInclusive();
+ }
+ };
+ }
+
+ @Override
+ public String asString() {
+ return delegate.toString();
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DefaultVersionRange that = (DefaultVersionRange) o;
+ return delegate.equals(that.delegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+ }
+
+ static class DefaultVersionConstraint implements VersionConstraint {
+ private final VersionScheme versionScheme;
+ private final org.eclipse.aether.version.VersionConstraint delegate;
+
+ DefaultVersionConstraint(VersionScheme versionScheme, String delegateValue) {
+ this.versionScheme = versionScheme;
+ try {
+ this.delegate = versionScheme.parseVersionConstraint(delegateValue);
+ } catch (InvalidVersionSpecificationException e) {
+ throw new VersionParserException("Unable to parse version constraint: " + delegateValue, e);
+ }
+ }
+
+ @Override
+ public boolean contains(Version version) {
+ if (version instanceof DefaultVersion) {
+ return delegate.containsVersion(((DefaultVersion) version).delegate);
+ } else {
+ return contains(new DefaultVersion(versionScheme, version.asString()));
+ }
+ }
+
+ @Override
+ public String asString() {
+ return delegate.toString();
+ }
+
+ @Override
+ public VersionRange getVersionRange() {
+ if (delegate.getRange() == null) {
+ return null;
+ }
+ return new DefaultVersionRange(versionScheme, delegate.getRange());
+ }
+
+ @Override
+ public Version getRecommendedVersion() {
+ if (delegate.getVersion() == null) {
+ return null;
+ }
+ return new DefaultVersion(versionScheme, delegate.getVersion());
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DefaultVersionConstraint that = (DefaultVersionConstraint) o;
+ return delegate.equals(that.delegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultPluginConfigurationExpander.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultPluginConfigurationExpander.java
new file mode 100644
index 000000000000..57cc19404dfd
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultPluginConfigurationExpander.java
@@ -0,0 +1,107 @@
+/*
+ * 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.internal.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Build;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.model.PluginManagement;
+import org.apache.maven.api.model.ReportPlugin;
+import org.apache.maven.api.model.Reporting;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.model.ModelProblemCollector;
+import org.apache.maven.api.services.model.PluginConfigurationExpander;
+import org.apache.maven.api.xml.XmlNode;
+
+/**
+ * Handles expansion of general build plugin configuration into individual executions.
+ *
+ */
+@Named
+@Singleton
+public class DefaultPluginConfigurationExpander implements PluginConfigurationExpander {
+
+ @Override
+ public Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Build build = model.getBuild();
+ if (build != null) {
+ build = build.withPlugins(expandPlugin(build.getPlugins()));
+ PluginManagement pluginManagement = build.getPluginManagement();
+ if (pluginManagement != null) {
+ build = build.withPluginManagement(
+ pluginManagement.withPlugins(expandPlugin(pluginManagement.getPlugins())));
+ }
+ model = model.withBuild(build);
+ }
+ Reporting reporting = model.getReporting();
+ if (reporting != null) {
+ expandReport(reporting.getPlugins());
+ }
+ return model.withBuild(build);
+ }
+
+ private List expandPlugin(List oldPlugins) {
+ return map(oldPlugins, plugin -> {
+ XmlNode pluginConfiguration = plugin.getConfiguration();
+ if (pluginConfiguration != null) {
+ return plugin.withExecutions(map(
+ plugin.getExecutions(),
+ execution -> execution.withConfiguration(
+ XmlNode.merge(execution.getConfiguration(), pluginConfiguration))));
+ } else {
+ return plugin;
+ }
+ });
+ }
+
+ private List expandReport(List oldPlugins) {
+ return map(oldPlugins, plugin -> {
+ XmlNode pluginConfiguration = plugin.getConfiguration();
+ if (pluginConfiguration != null) {
+ return plugin.withReportSets(map(
+ plugin.getReportSets(),
+ report -> report.withConfiguration(
+ XmlNode.merge(report.getConfiguration(), pluginConfiguration))));
+ } else {
+ return plugin;
+ }
+ });
+ }
+
+ static List map(List list, Function mapper) {
+ List newList = list;
+ for (int i = 0; i < newList.size(); i++) {
+ T oldT = newList.get(i);
+ T newT = mapper.apply(oldT);
+ if (newT != oldT) {
+ if (newList == list) {
+ newList = new ArrayList<>(list);
+ }
+ newList.set(i, newT);
+ }
+ }
+ return newList;
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSettingsBuilder.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSettingsBuilder.java
index 4801258d289f..24b64f6436fc 100644
--- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSettingsBuilder.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSettingsBuilder.java
@@ -137,11 +137,7 @@ private Settings readSettings(
Settings settings;
try {
- try {
- InputStream is = settingsSource.openStream();
- if (is == null) {
- return Settings.newInstance();
- }
+ try (InputStream is = settingsSource.openStream()) {
settings = request.getSession()
.getService(SettingsXmlFactory.class)
.read(XmlReaderRequest.builder()
@@ -150,25 +146,23 @@ private Settings readSettings(
.strict(true)
.build());
} catch (XmlReaderException e) {
- InputStream is = settingsSource.openStream();
- if (is == null) {
- return Settings.newInstance();
+ try (InputStream is = settingsSource.openStream()) {
+ settings = request.getSession()
+ .getService(SettingsXmlFactory.class)
+ .read(XmlReaderRequest.builder()
+ .inputStream(is)
+ .location(settingsSource.getLocation())
+ .strict(false)
+ .build());
+ Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
+ problems.add(new DefaultBuilderProblem(
+ settingsSource.getLocation(),
+ loc != null ? loc.getLineNumber() : -1,
+ loc != null ? loc.getColumnNumber() : -1,
+ e,
+ e.getMessage(),
+ BuilderProblem.Severity.WARNING));
}
- settings = request.getSession()
- .getService(SettingsXmlFactory.class)
- .read(XmlReaderRequest.builder()
- .inputStream(is)
- .location(settingsSource.getLocation())
- .strict(false)
- .build());
- Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
- problems.add(new DefaultBuilderProblem(
- settingsSource.getLocation(),
- loc != null ? loc.getLineNumber() : -1,
- loc != null ? loc.getColumnNumber() : -1,
- e,
- e.getMessage(),
- BuilderProblem.Severity.WARNING));
}
} catch (XmlReaderException e) {
Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSuperPomProvider.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSuperPomProvider.java
new file mode 100644
index 000000000000..58f75895a42a
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultSuperPomProvider.java
@@ -0,0 +1,80 @@
+/*
+ * 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.internal.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.SuperPomProvider;
+import org.apache.maven.api.services.model.ModelProcessor;
+import org.apache.maven.api.services.xml.XmlReaderRequest;
+
+@Named
+@Singleton
+public class DefaultSuperPomProvider implements SuperPomProvider {
+
+ private final ModelProcessor modelProcessor;
+
+ /**
+ * The cached super POM, lazily created.
+ */
+ private static final Map SUPER_MODELS = new ConcurrentHashMap<>();
+
+ @Inject
+ public DefaultSuperPomProvider(ModelProcessor modelProcessor) {
+ this.modelProcessor = modelProcessor;
+ }
+
+ @Override
+ public Model getSuperPom(String version) {
+ return SUPER_MODELS.computeIfAbsent(Objects.requireNonNull(version), v -> readModel(version, v));
+ }
+
+ private Model readModel(String version, String v) {
+ String resource = "/org/apache/maven/model/pom-" + v + ".xml";
+ URL url = getClass().getResource(resource);
+ if (url == null) {
+ throw new IllegalStateException("The super POM " + resource + " was not found"
+ + ", please verify the integrity of your Maven installation");
+ }
+ try (InputStream is = url.openStream()) {
+ String modelId = "org.apache.maven:maven-model-builder:" + version + "-"
+ + this.getClass().getPackage().getImplementationVersion() + ":super-pom";
+ return modelProcessor.read(XmlReaderRequest.builder()
+ .modelId(modelId)
+ .location(url.toExternalForm())
+ .inputStream(is)
+ .strict(false)
+ .build());
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "The super POM " + resource + " is damaged"
+ + ", please verify the integrity of your Maven installation",
+ e);
+ }
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultUrlNormalizer.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultUrlNormalizer.java
new file mode 100644
index 000000000000..c3b7bcb5a5d9
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultUrlNormalizer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.impl;
+
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.services.model.UrlNormalizer;
+
+/**
+ * Normalizes a URL.
+ *
+ */
+@Named
+@Singleton
+public class DefaultUrlNormalizer implements UrlNormalizer {
+
+ @Override
+ public String normalize(String url) {
+ String result = url;
+
+ if (result != null) {
+ while (true) {
+ int idx = result.indexOf("/../");
+ if (idx < 0) {
+ break;
+ } else if (idx == 0) {
+ result = result.substring(3);
+ continue;
+ }
+ int parent = idx - 1;
+ while (parent >= 0 && result.charAt(parent) == '/') {
+ parent--;
+ }
+ parent = result.lastIndexOf('/', parent);
+ if (parent < 0) {
+ result = result.substring(idx + 4);
+ } else {
+ result = result.substring(0, parent) + result.substring(idx + 3);
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionParser.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionParser.java
index 7e8ae11cbcd0..16d7b670bfab 100644
--- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionParser.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionParser.java
@@ -25,7 +25,7 @@
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.VersionParser;
-import org.apache.maven.model.version.ModelVersionParser;
+import org.apache.maven.api.services.model.ModelVersionParser;
import static org.apache.maven.internal.impl.Utils.nonNull;
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionRangeResolver.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionRangeResolver.java
index 41e107698eca..585fb6faf170 100644
--- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionRangeResolver.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultVersionRangeResolver.java
@@ -66,8 +66,9 @@ public VersionRangeResolverResult resolve(VersionRangeResolverRequest request)
session.toRepositories(session.getRemoteRepositories()),
null));
- Map repos =
- res.getVersions().stream().collect(Collectors.toMap(v -> v.toString(), res::getRepository));
+ Map repos = res.getVersions().stream()
+ .filter(v -> res.getRepository(v) != null)
+ .collect(Collectors.toMap(v -> v.toString(), res::getRepository));
return new VersionRangeResolverResult() {
@Override
diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
similarity index 86%
rename from maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
rename to maven-api-impl/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
index d512e74b4635..429f9339b766 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
@@ -18,18 +18,14 @@
*/
package org.apache.maven.internal.impl;
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.apache.maven.SessionScoped;
import org.apache.maven.api.*;
+import org.apache.maven.api.di.*;
import org.apache.maven.api.services.*;
import org.apache.maven.api.spi.*;
@@ -73,7 +69,19 @@ public DefaultLanguageRegistry(List providers) {
}
}
- static class DefaultExtensibleEnumRegistry>
+ @Named
+ @Singleton
+ public static class DefaultTypeRegistry extends DefaultExtensibleEnumRegistry
+ implements TypeRegistry {
+
+ @Inject
+ public DefaultTypeRegistry(List providers) {
+ super(providers);
+ }
+ }
+
+ public abstract static class DefaultExtensibleEnumRegistry<
+ T extends ExtensibleEnum, P extends ExtensibleEnumProvider>
implements ExtensibleEnumRegistry {
private final Map values;
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/InternalSession.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/InternalSession.java
index 0b5bf1874cca..5596cf3fdcb5 100644
--- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/InternalSession.java
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/InternalSession.java
@@ -20,6 +20,7 @@
import java.util.Collection;
import java.util.List;
+import java.util.function.Supplier;
import org.apache.maven.api.Artifact;
import org.apache.maven.api.ArtifactCoordinate;
@@ -41,6 +42,21 @@ static InternalSession from(Session session) {
return cast(InternalSession.class, session, "session should be an " + InternalSession.class);
}
+ static InternalSession from(org.eclipse.aether.RepositorySystemSession session) {
+ return cast(
+ InternalSession.class,
+ session.getData().get(InternalSession.class),
+ "session should be an " + InternalSession.class);
+ }
+
+ static InternalSession from(
+ org.eclipse.aether.RepositorySystemSession session, Supplier supplier) {
+ return cast(
+ InternalSession.class,
+ session.getData().computeIfAbsent(InternalSession.class, (Supplier) supplier),
+ "session should be an " + InternalSession.class);
+ }
+
RemoteRepository getRemoteRepository(org.eclipse.aether.repository.RemoteRepository repository);
Node getNode(org.eclipse.aether.graph.DependencyNode node);
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/BuildModelSourceTransformer.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/BuildModelSourceTransformer.java
new file mode 100644
index 000000000000..29772b4d0651
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/BuildModelSourceTransformer.java
@@ -0,0 +1,199 @@
+/*
+ * 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.internal.impl.model;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Parent;
+import org.apache.maven.api.services.model.*;
+import org.apache.maven.internal.impl.model.DefaultTransformerContext.*;
+
+/**
+ * ModelSourceTransformer for the build pom
+ *
+ * @since 4.0.0
+ */
+@Named
+@Singleton
+public class BuildModelSourceTransformer implements ModelSourceTransformer {
+
+ public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
+
+ @Override
+ public Model transform(Path pomFile, TransformerContext context, Model model) {
+ Model.Builder builder = Model.newBuilder(model);
+ handleModelVersion(model, builder);
+ handleParent(pomFile, context, model, builder);
+ handleReactorDependencies(context, model, builder);
+ handleCiFriendlyVersion(context, model, builder);
+ return builder.build();
+ }
+
+ //
+ // Infer modelVersion from namespace URI
+ //
+ void handleModelVersion(Model model, Model.Builder builder) {
+ String namespace = model.getNamespaceUri();
+ if (model.getModelVersion() == null && namespace != null && namespace.startsWith(NAMESPACE_PREFIX)) {
+ builder.modelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
+ }
+ }
+
+ //
+ // Infer parent information
+ //
+ void handleParent(Path pomFile, TransformerContext context, Model model, Model.Builder builder) {
+ Parent parent = model.getParent();
+ if (parent != null) {
+ String version = parent.getVersion();
+ String path = Optional.ofNullable(parent.getRelativePath()).orElse("..");
+ if (version == null && !path.isEmpty()) {
+ Optional resolvedParent = resolveRelativePath(
+ pomFile, context, Paths.get(path), parent.getGroupId(), parent.getArtifactId());
+ if (resolvedParent.isPresent()) {
+ version = resolvedParent.get().getVersion();
+ }
+ }
+ // CI Friendly version for parent
+ String modVersion = replaceCiFriendlyVersion(context, version);
+ builder.parent(parent.withVersion(modVersion));
+ }
+ }
+
+ //
+ // CI friendly versions
+ //
+ void handleCiFriendlyVersion(TransformerContext context, Model model, Model.Builder builder) {
+ String version = model.getVersion();
+ String modVersion = replaceCiFriendlyVersion(context, version);
+ builder.version(modVersion);
+ }
+
+ //
+ // Infer inner reactor dependencies version
+ //
+ void handleReactorDependencies(TransformerContext context, Model model, Model.Builder builder) {
+ List newDeps = new ArrayList<>();
+ boolean modified = false;
+ for (Dependency dep : model.getDependencies()) {
+ if (dep.getVersion() == null) {
+ Model depModel = context.getRawModel(model.getPomFile(), dep.getGroupId(), dep.getArtifactId());
+ if (depModel != null) {
+ String v = depModel.getVersion();
+ if (v == null && depModel.getParent() != null) {
+ v = depModel.getParent().getVersion();
+ }
+ dep = dep.withVersion(v);
+ modified = true;
+ }
+ }
+ newDeps.add(dep);
+ }
+ if (modified) {
+ builder.dependencies(newDeps);
+ }
+ }
+
+ protected String replaceCiFriendlyVersion(TransformerContext context, String version) {
+ if (version != null) {
+ for (String key : Arrays.asList("changelist", "revision", "sha1")) {
+ String val = context.getUserProperty(key);
+ if (val != null) {
+ version = version.replace("${" + key + "}", val);
+ }
+ }
+ }
+ return version;
+ }
+
+ protected Optional resolveRelativePath(
+ Path pomFile, TransformerContext context, Path relativePath, String groupId, String artifactId) {
+ Path pomPath = pomFile.resolveSibling(relativePath).normalize();
+ if (Files.isDirectory(pomPath)) {
+ pomPath = context.locate(pomPath);
+ }
+
+ if (pomPath == null || !Files.isRegularFile(pomPath)) {
+ return Optional.empty();
+ }
+
+ Optional mappedProject = Optional.ofNullable(context.getRawModel(pomFile, pomPath.normalize()))
+ .map(BuildModelSourceTransformer::toRelativeProject);
+
+ if (mappedProject.isPresent()) {
+ RelativeProject project = mappedProject.get();
+
+ if (Objects.equals(groupId, project.getGroupId()) && Objects.equals(artifactId, project.getArtifactId())) {
+ return mappedProject;
+ }
+ }
+ return Optional.empty();
+ }
+
+ private static RelativeProject toRelativeProject(final Model m) {
+ String groupId = m.getGroupId();
+ if (groupId == null && m.getParent() != null) {
+ groupId = m.getParent().getGroupId();
+ }
+
+ String version = m.getVersion();
+ if (version == null && m.getParent() != null) {
+ version = m.getParent().getVersion();
+ }
+
+ return new RelativeProject(groupId, m.getArtifactId(), version);
+ }
+
+ static class RelativeProject {
+ private final String groupId;
+
+ private final String artifactId;
+
+ private final String version;
+
+ RelativeProject(String groupId, String artifactId, String version) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementImporter.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementImporter.java
new file mode 100644
index 000000000000..b46a4e673a00
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementImporter.java
@@ -0,0 +1,147 @@
+/*
+ * 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.internal.impl.model;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
+import org.apache.maven.api.model.Exclusion;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.BuilderProblem.Severity;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.ModelProblem.Version;
+import org.apache.maven.api.services.model.*;
+import org.apache.maven.internal.impl.model.DefaultTransformerContext.*;
+
+/**
+ * Handles the import of dependency management from other models into the target model.
+ *
+ */
+@Named
+@Singleton
+public class DefaultDependencyManagementImporter implements DependencyManagementImporter {
+
+ @Override
+ public Model importManagement(
+ Model target,
+ List extends DependencyManagement> sources,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ if (sources != null && !sources.isEmpty()) {
+ Map dependencies = new LinkedHashMap<>();
+
+ DependencyManagement depMgmt = target.getDependencyManagement();
+
+ if (depMgmt != null) {
+ for (Dependency dependency : depMgmt.getDependencies()) {
+ dependencies.put(dependency.getManagementKey(), dependency);
+ }
+ } else {
+ depMgmt = DependencyManagement.newInstance();
+ }
+
+ Set directDependencies = new HashSet<>(dependencies.keySet());
+
+ for (DependencyManagement source : sources) {
+ for (Dependency dependency : source.getDependencies()) {
+ String key = dependency.getManagementKey();
+ Dependency present = dependencies.putIfAbsent(key, dependency);
+ if (present != null && !equals(dependency, present) && !directDependencies.contains(key)) {
+ // TODO: https://issues.apache.org/jira/browse/MNG-8004
+ problems.add(
+ Severity.WARNING,
+ Version.V40,
+ "Ignored POM import for: " + toString(dependency) + " as already imported "
+ + toString(present) + ". Add a the conflicting managed dependency directly "
+ + "to the dependencyManagement section of the POM.");
+ }
+ }
+ }
+
+ return target.withDependencyManagement(depMgmt.withDependencies(dependencies.values()));
+ }
+ return target;
+ }
+
+ private String toString(Dependency dependency) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder
+ .append(dependency.getGroupId())
+ .append(":")
+ .append(dependency.getArtifactId())
+ .append(":")
+ .append(dependency.getType());
+ if (dependency.getClassifier() != null && !dependency.getClassifier().isEmpty()) {
+ stringBuilder.append(":").append(dependency.getClassifier());
+ }
+ stringBuilder
+ .append(":")
+ .append(dependency.getVersion())
+ .append("@")
+ .append(dependency.getScope() == null ? "compile" : dependency.getScope());
+ if (dependency.isOptional()) {
+ stringBuilder.append("[optional]");
+ }
+ if (!dependency.getExclusions().isEmpty()) {
+ stringBuilder.append("[").append(dependency.getExclusions().size()).append(" exclusions]");
+ }
+ return stringBuilder.toString();
+ }
+
+ private boolean equals(Dependency d1, Dependency d2) {
+ return Objects.equals(d1.getGroupId(), d2.getGroupId())
+ && Objects.equals(d1.getArtifactId(), d2.getArtifactId())
+ && Objects.equals(d1.getVersion(), d2.getVersion())
+ && Objects.equals(d1.getType(), d2.getType())
+ && Objects.equals(d1.getClassifier(), d2.getClassifier())
+ && Objects.equals(d1.getScope(), d2.getScope())
+ && Objects.equals(d1.getSystemPath(), d2.getSystemPath())
+ && Objects.equals(d1.getOptional(), d2.getOptional())
+ && equals(d1.getExclusions(), d2.getExclusions());
+ }
+
+ private boolean equals(Collection ce1, Collection ce2) {
+ if (ce1.size() == ce2.size()) {
+ Iterator i1 = ce1.iterator();
+ Iterator i2 = ce2.iterator();
+ while (i1.hasNext() && i2.hasNext()) {
+ if (!equals(i1.next(), i2.next())) {
+ return false;
+ }
+ }
+ return !i1.hasNext() && !i2.hasNext();
+ }
+ return false;
+ }
+
+ private boolean equals(Exclusion e1, Exclusion e2) {
+ return Objects.equals(e1.getGroupId(), e2.getGroupId())
+ && Objects.equals(e1.getArtifactId(), e2.getArtifactId());
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementInjector.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementInjector.java
new file mode 100644
index 000000000000..5a1c4541325a
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultDependencyManagementInjector.java
@@ -0,0 +1,118 @@
+/*
+ * 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.internal.impl.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
+import org.apache.maven.api.model.Exclusion;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.model.*;
+
+/**
+ * Handles injection of dependency management into the model.
+ *
+ */
+@SuppressWarnings({"checkstyle:methodname"})
+@Named
+@Singleton
+public class DefaultDependencyManagementInjector implements DependencyManagementInjector {
+
+ private ManagementModelMerger merger = new ManagementModelMerger();
+
+ @Override
+ public Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
+ return merger.mergeManagedDependencies(model);
+ }
+
+ /**
+ * ManagementModelMerger
+ */
+ protected static class ManagementModelMerger extends MavenModelMerger {
+
+ public Model mergeManagedDependencies(Model model) {
+ DependencyManagement dependencyManagement = model.getDependencyManagement();
+ if (dependencyManagement != null) {
+ Map
+ *
+ * @param child The child model, must not be null.
+ * @param parent The parent model, may be null.
+ * @param childDirectory The directory defined in child model, may be null.
+ * @return The path adjustment, can be empty but never null.
+ */
+ private String getChildPathAdjustment(Model child, Model parent, String childDirectory) {
+ String adjustment = "";
+
+ if (parent != null) {
+ String childName = child.getArtifactId();
+
+ /*
+ * This logic (using filesystem, against wanted independence from the user environment) exists only for the
+ * sake of backward-compat with 2.x (MNG-5000). In general, it is wrong to
+ * base URL inheritance on the module directory names as this information is unavailable for POMs in the
+ * repository. In other words, modules where artifactId != moduleDirName will see different effective URLs
+ * depending on how the model was constructed (from filesystem or from repository).
+ */
+ if (child.getProjectDirectory() != null) {
+ childName = child.getProjectDirectory().getFileName().toString();
+ }
+
+ for (String module : parent.getModules()) {
+ module = module.replace('\\', '/');
+
+ if (module.regionMatches(true, module.length() - 4, ".xml", 0, 4)) {
+ module = module.substring(0, module.lastIndexOf('/') + 1);
+ }
+
+ String moduleName = module;
+ if (moduleName.endsWith("/")) {
+ moduleName = moduleName.substring(0, moduleName.length() - 1);
+ }
+
+ int lastSlash = moduleName.lastIndexOf('/');
+
+ moduleName = moduleName.substring(lastSlash + 1);
+
+ if ((moduleName.equals(childName) || (moduleName.equals(childDirectory))) && lastSlash >= 0) {
+ adjustment = module.substring(0, lastSlash);
+ break;
+ }
+ }
+ }
+
+ return adjustment;
+ }
+
+ /**
+ * InheritanceModelMerger
+ */
+ protected static class InheritanceModelMerger extends MavenModelMerger {
+
+ @Override
+ protected String extrapolateChildUrl(String parentUrl, boolean appendPath, Map context) {
+ Object childDirectory = context.get(CHILD_DIRECTORY);
+ Object childPathAdjustment = context.get(CHILD_PATH_ADJUSTMENT);
+
+ boolean isBlankParentUrl = true;
+
+ if (parentUrl != null) {
+ for (int i = 0; i < parentUrl.length(); i++) {
+ if (!Character.isWhitespace(parentUrl.charAt(i))) {
+ isBlankParentUrl = false;
+ }
+ }
+ }
+
+ if (isBlankParentUrl || childDirectory == null || childPathAdjustment == null || !appendPath) {
+ return parentUrl;
+ }
+
+ // append childPathAdjustment and childDirectory to parent url
+ return appendPath(parentUrl, childDirectory.toString(), childPathAdjustment.toString());
+ }
+
+ private String appendPath(String parentUrl, String childPath, String pathAdjustment) {
+ StringBuilder url = new StringBuilder(parentUrl.length()
+ + pathAdjustment.length()
+ + childPath.length()
+ + (pathAdjustment.isEmpty() ? 1 : 2));
+
+ url.append(parentUrl);
+ concatPath(url, pathAdjustment);
+ concatPath(url, childPath);
+
+ return url.toString();
+ }
+
+ private void concatPath(StringBuilder url, String path) {
+ if (!path.isEmpty()) {
+ boolean initialUrlEndsWithSlash = url.charAt(url.length() - 1) == '/';
+ boolean pathStartsWithSlash = path.charAt(0) == '/';
+
+ if (pathStartsWithSlash) {
+ if (initialUrlEndsWithSlash) {
+ // 1 extra '/' to remove
+ url.setLength(url.length() - 1);
+ }
+ } else if (!initialUrlEndsWithSlash) {
+ // add missing '/' between url and path
+ url.append('/');
+ }
+
+ url.append(path);
+
+ // ensure resulting url ends with slash if initial url was
+ if (initialUrlEndsWithSlash && !path.endsWith("/")) {
+ url.append('/');
+ }
+ }
+ }
+
+ @Override
+ protected void mergeModelBase_Properties(
+ ModelBase.Builder builder,
+ ModelBase target,
+ ModelBase source,
+ boolean sourceDominant,
+ Map context) {
+ Map merged = new HashMap<>();
+ if (sourceDominant) {
+ merged.putAll(target.getProperties());
+ putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
+ } else {
+ putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
+ merged.putAll(target.getProperties());
+ }
+ builder.properties(merged);
+ builder.location(
+ "properties",
+ InputLocation.merge(
+ target.getLocation("properties"), source.getLocation("properties"), sourceDominant));
+ }
+
+ private void putAll(Map s, Map t, Object excludeKey) {
+ for (Map.Entry e : t.entrySet()) {
+ if (!e.getKey().equals(excludeKey)) {
+ s.put(e.getKey(), e.getValue());
+ }
+ }
+ }
+
+ @Override
+ protected void mergePluginContainer_Plugins(
+ PluginContainer.Builder builder,
+ PluginContainer target,
+ PluginContainer source,
+ boolean sourceDominant,
+ Map context) {
+ List src = source.getPlugins();
+ if (!src.isEmpty()) {
+ List tgt = target.getPlugins();
+ Map master = new LinkedHashMap<>(src.size() * 2);
+
+ for (Plugin element : src) {
+ if (element.isInherited() || !element.getExecutions().isEmpty()) {
+ // NOTE: Enforce recursive merge to trigger merging/inheritance logic for executions
+ Plugin plugin = Plugin.newInstance(false);
+ plugin = mergePlugin(plugin, element, sourceDominant, context);
+
+ Object key = getPluginKey().apply(plugin);
+
+ master.put(key, plugin);
+ }
+ }
+
+ Map> predecessors = new LinkedHashMap<>();
+ List pending = new ArrayList<>();
+ for (Plugin element : tgt) {
+ Object key = getPluginKey().apply(element);
+ Plugin existing = master.get(key);
+ if (existing != null) {
+ element = mergePlugin(element, existing, sourceDominant, context);
+
+ master.put(key, element);
+
+ if (!pending.isEmpty()) {
+ predecessors.put(key, pending);
+ pending = new ArrayList<>();
+ }
+ } else {
+ pending.add(element);
+ }
+ }
+
+ List result = new ArrayList<>(src.size() + tgt.size());
+ for (Map.Entry entry : master.entrySet()) {
+ List pre = predecessors.get(entry.getKey());
+ if (pre != null) {
+ result.addAll(pre);
+ }
+ result.add(entry.getValue());
+ }
+ result.addAll(pending);
+
+ builder.plugins(result);
+ }
+ }
+
+ @Override
+ protected Plugin mergePlugin(
+ Plugin target, Plugin source, boolean sourceDominant, Map context) {
+ Plugin.Builder builder = Plugin.newBuilder(target);
+ if (source.isInherited()) {
+ mergeConfigurationContainer(builder, target, source, sourceDominant, context);
+ }
+ mergePlugin_GroupId(builder, target, source, sourceDominant, context);
+ mergePlugin_ArtifactId(builder, target, source, sourceDominant, context);
+ mergePlugin_Version(builder, target, source, sourceDominant, context);
+ mergePlugin_Extensions(builder, target, source, sourceDominant, context);
+ mergePlugin_Executions(builder, target, source, sourceDominant, context);
+ mergePlugin_Dependencies(builder, target, source, sourceDominant, context);
+ return builder.build();
+ }
+
+ @Override
+ protected void mergeReporting_Plugins(
+ Reporting.Builder builder,
+ Reporting target,
+ Reporting source,
+ boolean sourceDominant,
+ Map context) {
+ List src = source.getPlugins();
+ if (!src.isEmpty()) {
+ List tgt = target.getPlugins();
+ Map merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
+
+ for (ReportPlugin element : src) {
+ if (element.isInherited()) {
+ // NOTE: Enforce recursive merge to trigger merging/inheritance logic for executions as well
+ ReportPlugin plugin = ReportPlugin.newInstance(false);
+ plugin = mergeReportPlugin(plugin, element, sourceDominant, context);
+
+ merged.put(getReportPluginKey().apply(element), plugin);
+ }
+ }
+
+ for (ReportPlugin element : tgt) {
+ Object key = getReportPluginKey().apply(element);
+ ReportPlugin existing = merged.get(key);
+ if (existing != null) {
+ element = mergeReportPlugin(element, existing, sourceDominant, context);
+ }
+ merged.put(key, element);
+ }
+
+ builder.plugins(merged.values());
+ }
+ }
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultLifecycleBindingsInjector.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultLifecycleBindingsInjector.java
new file mode 100644
index 000000000000..709f0b92e46a
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultLifecycleBindingsInjector.java
@@ -0,0 +1,167 @@
+/*
+ * 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.internal.impl.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.api.Packaging;
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Build;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.model.PluginContainer;
+import org.apache.maven.api.model.PluginExecution;
+import org.apache.maven.api.model.PluginManagement;
+import org.apache.maven.api.services.BuilderProblem.Severity;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.ModelProblem.Version;
+import org.apache.maven.api.services.PackagingRegistry;
+import org.apache.maven.api.services.model.*;
+
+/**
+ * Handles injection of plugin executions induced by the lifecycle bindings for a packaging.
+ *
+ */
+@Named
+@Singleton
+public class DefaultLifecycleBindingsInjector implements LifecycleBindingsInjector {
+
+ private final LifecycleBindingsMerger merger = new LifecycleBindingsMerger();
+
+ private final PackagingRegistry packagingRegistry;
+
+ @Inject
+ public DefaultLifecycleBindingsInjector(PackagingRegistry packagingRegistry) {
+ this.packagingRegistry = packagingRegistry;
+ }
+
+ public Model injectLifecycleBindings(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
+ String packagingId = model.getPackaging();
+ Packaging packaging = packagingRegistry.lookup(packagingId).orElse(null);
+ if (packaging == null) {
+ problems.add(
+ Severity.ERROR, Version.BASE, "Unknown packaging: " + packaging, model.getLocation("packaging"));
+ return model;
+ } else {
+ Model lifecycleModel = Model.newBuilder()
+ .build(Build.newBuilder()
+ .plugins(packaging.plugins().getPlugins())
+ .build())
+ .build();
+ return merger.merge(model, lifecycleModel);
+ }
+ }
+
+ /**
+ * The domain-specific model merger for lifecycle bindings
+ */
+ protected static class LifecycleBindingsMerger extends MavenModelMerger {
+
+ private static final String PLUGIN_MANAGEMENT = "plugin-management";
+
+ public Model merge(Model target, Model source) {
+ Build targetBuild = target.getBuild();
+ if (targetBuild == null) {
+ targetBuild = Build.newInstance();
+ }
+
+ Map context = Collections.singletonMap(
+ PLUGIN_MANAGEMENT, target.getBuild().getPluginManagement());
+
+ Build.Builder builder = Build.newBuilder(target.getBuild());
+ mergePluginContainer_Plugins(builder, targetBuild, source.getBuild(), false, context);
+
+ return target.withBuild(builder.build());
+ }
+
+ @SuppressWarnings({"checkstyle:methodname"})
+ @Override
+ protected void mergePluginContainer_Plugins(
+ PluginContainer.Builder builder,
+ PluginContainer target,
+ PluginContainer source,
+ boolean sourceDominant,
+ Map context) {
+ List src = source.getPlugins();
+ if (!src.isEmpty()) {
+ List tgt = target.getPlugins();
+
+ Map merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
+
+ for (Plugin element : tgt) {
+ Object key = getPluginKey().apply(element);
+ merged.put(key, element);
+ }
+
+ Map added = new LinkedHashMap<>();
+
+ for (Plugin element : src) {
+ Object key = getPluginKey().apply(element);
+ Plugin existing = merged.get(key);
+ if (existing != null) {
+ element = mergePlugin(existing, element, sourceDominant, context);
+ } else {
+ added.put(key, element);
+ }
+ merged.put(key, element);
+ }
+
+ if (!added.isEmpty()) {
+ PluginManagement pluginMgmt = (PluginManagement) context.get(PLUGIN_MANAGEMENT);
+ if (pluginMgmt != null) {
+ for (Plugin managedPlugin : pluginMgmt.getPlugins()) {
+ Object key = getPluginKey().apply(managedPlugin);
+ Plugin addedPlugin = added.get(key);
+ if (addedPlugin != null) {
+ Plugin plugin =
+ mergePlugin(managedPlugin, addedPlugin, sourceDominant, Collections.emptyMap());
+ merged.put(key, plugin);
+ }
+ }
+ }
+ }
+
+ List result = new ArrayList<>(merged.values());
+
+ builder.plugins(result);
+ }
+ }
+
+ @Override
+ protected void mergePluginExecution_Priority(
+ PluginExecution.Builder builder,
+ PluginExecution target,
+ PluginExecution source,
+ boolean sourceDominant,
+ Map context) {
+ if (target.getPriority() > source.getPriority()) {
+ builder.priority(source.getPriority());
+ builder.location("priority", source.getLocation("priority"));
+ }
+ }
+ // mergePluginExecution_Priority( builder, target, source, sourceDominant, context );
+
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
new file mode 100644
index 000000000000..4b639fccd6c4
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
@@ -0,0 +1,1382 @@
+/*
+ * 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.internal.impl.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.maven.api.VersionRange;
+import org.apache.maven.api.annotations.Nullable;
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.feature.Features;
+import org.apache.maven.api.model.Activation;
+import org.apache.maven.api.model.ActivationFile;
+import org.apache.maven.api.model.Build;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
+import org.apache.maven.api.model.Exclusion;
+import org.apache.maven.api.model.InputLocation;
+import org.apache.maven.api.model.InputSource;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Parent;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.model.PluginManagement;
+import org.apache.maven.api.model.Profile;
+import org.apache.maven.api.services.BuilderProblem.Severity;
+import org.apache.maven.api.services.ModelBuilder;
+import org.apache.maven.api.services.ModelBuilderException;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.ModelBuilderResult;
+import org.apache.maven.api.services.ModelCache;
+import org.apache.maven.api.services.ModelProblem;
+import org.apache.maven.api.services.ModelResolver;
+import org.apache.maven.api.services.ModelSource;
+import org.apache.maven.api.services.Source;
+import org.apache.maven.api.services.SuperPomProvider;
+import org.apache.maven.api.services.VersionParserException;
+import org.apache.maven.api.services.model.*;
+import org.apache.maven.api.services.xml.XmlReaderException;
+import org.apache.maven.api.services.xml.XmlReaderRequest;
+import org.apache.maven.internal.impl.InternalSession;
+import org.apache.maven.internal.impl.resolver.DefaultModelResolver;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.MapBasedValueSource;
+import org.codehaus.plexus.interpolation.StringSearchInterpolator;
+import org.eclipse.aether.impl.RemoteRepositoryManager;
+
+/**
+ */
+@Named
+@Singleton
+public class DefaultModelBuilder implements ModelBuilder {
+
+ private static final String RAW = "raw";
+ private static final String FILE = "file";
+ private static final String IMPORT = "import";
+
+ private final ModelProcessor modelProcessor;
+ private final ModelValidator modelValidator;
+ private final ModelNormalizer modelNormalizer;
+ private final ModelInterpolator modelInterpolator;
+ private final ModelPathTranslator modelPathTranslator;
+ private final ModelUrlNormalizer modelUrlNormalizer;
+ private final SuperPomProvider superPomProvider;
+ private final InheritanceAssembler inheritanceAssembler;
+ private final ProfileSelector profileSelector;
+ private final ProfileInjector profileInjector;
+ private final PluginManagementInjector pluginManagementInjector;
+ private final DependencyManagementInjector dependencyManagementInjector;
+ private final DependencyManagementImporter dependencyManagementImporter;
+ private final LifecycleBindingsInjector lifecycleBindingsInjector;
+ private final PluginConfigurationExpander pluginConfigurationExpander;
+ private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator;
+ private final ModelSourceTransformer transformer;
+ private final ModelVersionParser versionParser;
+ private final RemoteRepositoryManager remoteRepositoryManager;
+
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ @Inject
+ public DefaultModelBuilder(
+ ModelProcessor modelProcessor,
+ ModelValidator modelValidator,
+ ModelNormalizer modelNormalizer,
+ ModelInterpolator modelInterpolator,
+ ModelPathTranslator modelPathTranslator,
+ ModelUrlNormalizer modelUrlNormalizer,
+ SuperPomProvider superPomProvider,
+ InheritanceAssembler inheritanceAssembler,
+ ProfileSelector profileSelector,
+ ProfileInjector profileInjector,
+ PluginManagementInjector pluginManagementInjector,
+ DependencyManagementInjector dependencyManagementInjector,
+ DependencyManagementImporter dependencyManagementImporter,
+ @Nullable LifecycleBindingsInjector lifecycleBindingsInjector,
+ PluginConfigurationExpander pluginConfigurationExpander,
+ ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator,
+ ModelSourceTransformer transformer,
+ ModelVersionParser versionParser,
+ RemoteRepositoryManager remoteRepositoryManager) {
+ this.modelProcessor = modelProcessor;
+ this.modelValidator = modelValidator;
+ this.modelNormalizer = modelNormalizer;
+ this.modelInterpolator = modelInterpolator;
+ this.modelPathTranslator = modelPathTranslator;
+ this.modelUrlNormalizer = modelUrlNormalizer;
+ this.superPomProvider = superPomProvider;
+ this.inheritanceAssembler = inheritanceAssembler;
+ this.profileSelector = profileSelector;
+ this.profileInjector = profileInjector;
+ this.pluginManagementInjector = pluginManagementInjector;
+ this.dependencyManagementInjector = dependencyManagementInjector;
+ this.dependencyManagementImporter = dependencyManagementImporter;
+ this.lifecycleBindingsInjector = lifecycleBindingsInjector;
+ this.pluginConfigurationExpander = pluginConfigurationExpander;
+ this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator;
+ this.transformer = transformer;
+ this.versionParser = versionParser;
+ this.remoteRepositoryManager = remoteRepositoryManager;
+ }
+
+ public DefaultTransformerContextBuilder newTransformerContextBuilder() {
+ return new DefaultTransformerContextBuilder(this);
+ }
+
+ @Override
+ public ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilderException {
+ return build(request, new LinkedHashSet<>());
+ }
+
+ protected ModelBuilderResult build(ModelBuilderRequest request, Collection importIds)
+ throws ModelBuilderException {
+ // phase 1
+ DefaultModelBuilderResult result = new DefaultModelBuilderResult();
+
+ DefaultModelProblemCollector problems = new DefaultModelProblemCollector(result);
+
+ // read and validate raw model
+ Model fileModel = readFileModel(request, problems);
+
+ // fileModel = activateFileModel(fileModel, request, result, problems);
+
+ result.setFileModel(fileModel);
+
+ if (!request.isTwoPhaseBuilding()) {
+ return build(request, result, importIds);
+ } else if (hasModelErrors(problems)) {
+ throw problems.newModelBuilderException();
+ }
+
+ return result;
+ }
+
+ private Model activateFileModel(
+ Model inputModel,
+ ModelBuilderRequest request,
+ DefaultModelBuilderResult result,
+ DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ problems.setRootModel(inputModel);
+
+ // profile activation
+ DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel);
+
+ problems.setSource("(external profiles)");
+ List activeExternalProfiles =
+ profileSelector.getActiveProfiles(request.getProfiles(), profileActivationContext, problems);
+
+ result.setActiveExternalProfiles(activeExternalProfiles);
+
+ if (!activeExternalProfiles.isEmpty()) {
+ Properties profileProps = new Properties();
+ for (Profile profile : activeExternalProfiles) {
+ profileProps.putAll(profile.getProperties());
+ }
+ profileProps.putAll(profileActivationContext.getUserProperties());
+ profileActivationContext.setUserProperties(profileProps);
+ }
+
+ profileActivationContext.setProjectProperties(inputModel.getProperties());
+ problems.setSource(inputModel);
+ List activePomProfiles =
+ profileSelector.getActiveProfiles(inputModel.getProfiles(), profileActivationContext, problems);
+
+ // model normalization
+ problems.setSource(inputModel);
+ inputModel = modelNormalizer.mergeDuplicates(inputModel, request, problems);
+
+ Map interpolatedActivations = getProfileActivations(inputModel);
+ inputModel = injectProfileActivations(inputModel, interpolatedActivations);
+
+ // profile injection
+ inputModel = profileInjector.injectProfiles(inputModel, activePomProfiles, request, problems);
+ inputModel = profileInjector.injectProfiles(inputModel, activeExternalProfiles, request, problems);
+
+ return inputModel;
+ }
+
+ @SuppressWarnings("checkstyle:methodlength")
+ private Model readEffectiveModel(
+ final ModelBuilderRequest request,
+ final DefaultModelBuilderResult result,
+ DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ Model inputModel = readRawModel(request, problems);
+ if (problems.hasFatalErrors()) {
+ throw problems.newModelBuilderException();
+ }
+
+ problems.setRootModel(inputModel);
+
+ ModelData resultData = new ModelData(request.getSource(), inputModel);
+ String superModelVersion = inputModel.getModelVersion() != null ? inputModel.getModelVersion() : "4.0.0";
+ if (!VALID_MODEL_VERSIONS.contains(superModelVersion)) {
+ // Maven 3.x is always using 4.0.0 version to load the supermodel, so
+ // do the same when loading a dependency. The model validator will also
+ // check that field later.
+ superModelVersion = "4.0.0";
+ }
+ ModelData superData = new ModelData(null, getSuperModel(superModelVersion));
+
+ // profile activation
+ DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel);
+
+ List activeExternalProfiles = result.getActiveExternalProfiles();
+
+ if (!activeExternalProfiles.isEmpty()) {
+ Properties profileProps = new Properties();
+ for (Profile profile : activeExternalProfiles) {
+ profileProps.putAll(profile.getProperties());
+ }
+ profileProps.putAll(profileActivationContext.getUserProperties());
+ profileActivationContext.setUserProperties(profileProps);
+ }
+
+ Collection parentIds = new LinkedHashSet<>();
+
+ List lineage = new ArrayList<>();
+
+ for (ModelData currentData = resultData; ; ) {
+ String modelId = currentData.id();
+ result.addModelId(modelId);
+
+ Model model = currentData.model();
+ result.setRawModel(modelId, model);
+ problems.setSource(model);
+
+ // model normalization
+ model = modelNormalizer.mergeDuplicates(model, request, problems);
+
+ // profile activation
+ profileActivationContext.setProjectProperties(model.getProperties());
+
+ List interpolatedProfiles =
+ interpolateActivations(model.getProfiles(), profileActivationContext, problems);
+
+ // profile injection
+ List activePomProfiles =
+ profileSelector.getActiveProfiles(interpolatedProfiles, profileActivationContext, problems);
+ result.setActivePomProfiles(modelId, activePomProfiles);
+ model = profileInjector.injectProfiles(model, activePomProfiles, request, problems);
+ if (currentData == resultData) {
+ model = profileInjector.injectProfiles(model, activeExternalProfiles, request, problems);
+ }
+
+ lineage.add(model);
+
+ if (currentData == superData) {
+ break;
+ }
+
+ // add repositories specified by the current model so that we can resolve the parent
+ configureResolver(getModelResolver(request), model, request, problems, false);
+
+ // we pass a cloned model, so that resolving the parent version does not affect the returned model
+ ModelData parentData = readParent(model, currentData.source(), request, problems);
+
+ if (parentData == null) {
+ currentData = superData;
+ } else if (!parentIds.add(parentData.id())) {
+ StringBuilder message = new StringBuilder("The parents form a cycle: ");
+ for (String parentId : parentIds) {
+ message.append(parentId).append(" -> ");
+ }
+ message.append(parentData.id());
+
+ problems.add(Severity.FATAL, ModelProblem.Version.BASE, message.toString());
+
+ throw problems.newModelBuilderException();
+ } else {
+ currentData = parentData;
+ }
+ }
+
+ Model tmpModel = lineage.get(0);
+
+ // inject interpolated activations
+ List interpolated = interpolateActivations(tmpModel.getProfiles(), profileActivationContext, problems);
+ if (interpolated != tmpModel.getProfiles()) {
+ tmpModel = tmpModel.withProfiles(interpolated);
+ }
+
+ // inject external profile into current model
+ tmpModel = profileInjector.injectProfiles(tmpModel, activeExternalProfiles, request, problems);
+
+ lineage.set(0, tmpModel);
+
+ checkPluginVersions(lineage, request, problems);
+
+ // inheritance assembly
+ Model resultModel = assembleInheritance(lineage, request, problems);
+
+ // consider caching inherited model
+
+ problems.setSource(resultModel);
+ problems.setRootModel(resultModel);
+
+ // model interpolation
+ resultModel = interpolateModel(resultModel, request, problems);
+
+ // url normalization
+ resultModel = modelUrlNormalizer.normalize(resultModel, request);
+
+ result.setEffectiveModel(resultModel);
+
+ // Now the fully interpolated model is available: reconfigure the resolver
+ configureResolver(getModelResolver(request), resultModel, request, problems, true);
+
+ return resultModel;
+ }
+
+ private List interpolateActivations(
+ List profiles, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) {
+ List newProfiles = null;
+ for (int index = 0; index < profiles.size(); index++) {
+ Profile profile = profiles.get(index);
+ Activation activation = profile.getActivation();
+ if (activation != null) {
+ ActivationFile file = activation.getFile();
+ if (file != null) {
+ String oldExists = file.getExists();
+ if (isNotEmpty(oldExists)) {
+ try {
+ String newExists = interpolate(oldExists, context);
+ if (!Objects.equals(oldExists, newExists)) {
+ if (newProfiles == null) {
+ newProfiles = new ArrayList<>(profiles);
+ }
+ newProfiles.set(
+ index, profile.withActivation(activation.withFile(file.withExists(newExists))));
+ }
+ } catch (InterpolationException e) {
+ addInterpolationProblem(problems, file, oldExists, e, "exists");
+ }
+ } else {
+ String oldMissing = file.getMissing();
+ if (isNotEmpty(oldMissing)) {
+ try {
+ String newMissing = interpolate(oldMissing, context);
+ if (!Objects.equals(oldMissing, newMissing)) {
+ if (newProfiles == null) {
+ newProfiles = new ArrayList<>(profiles);
+ }
+ newProfiles.set(
+ index,
+ profile.withActivation(activation.withFile(file.withMissing(newMissing))));
+ }
+ } catch (InterpolationException e) {
+ addInterpolationProblem(problems, file, oldMissing, e, "missing");
+ }
+ }
+ }
+ }
+ }
+ }
+ return newProfiles != null ? newProfiles : profiles;
+ }
+
+ private static void addInterpolationProblem(
+ DefaultModelProblemCollector problems,
+ ActivationFile file,
+ String path,
+ InterpolationException e,
+ String locationKey) {
+ problems.add(
+ Severity.ERROR,
+ ModelProblem.Version.BASE,
+ "Failed to interpolate file location " + path + ": " + e.getMessage(),
+ file.getLocation(locationKey),
+ e);
+ }
+
+ private String interpolate(String path, ProfileActivationContext context) throws InterpolationException {
+ return isNotEmpty(path) ? profileActivationFilePathInterpolator.interpolate(path, context) : path;
+ }
+
+ private static boolean isNotEmpty(String string) {
+ return string != null && !string.isEmpty();
+ }
+
+ public ModelBuilderResult build(final ModelBuilderRequest request, final ModelBuilderResult result)
+ throws ModelBuilderException {
+ return build(request, result, new LinkedHashSet<>());
+ }
+
+ public Model buildRawModel(final ModelBuilderRequest request) throws ModelBuilderException {
+ DefaultModelProblemCollector problems = new DefaultModelProblemCollector(new DefaultModelBuilderResult());
+ Model model = readRawModel(request, problems);
+ if (hasModelErrors(problems)) {
+ throw problems.newModelBuilderException();
+ }
+ return model;
+ }
+
+ private ModelBuilderResult build(
+ ModelBuilderRequest request, final ModelBuilderResult phaseOneResult, Collection importIds)
+ throws ModelBuilderException {
+ if (request.getModelResolver() == null) {
+ ModelResolver resolver = new DefaultModelResolver(
+ remoteRepositoryManager,
+ InternalSession.from(request.getSession())
+ .toRepositories(request.getSession().getRemoteRepositories()));
+ request =
+ ModelBuilderRequest.builder(request).modelResolver(resolver).build();
+ }
+
+ DefaultModelBuilderResult result = asDefaultModelBuilderResult(phaseOneResult);
+
+ DefaultModelProblemCollector problems = new DefaultModelProblemCollector(result);
+
+ // phase 2
+ Model resultModel = readEffectiveModel(request, result, problems);
+ problems.setSource(resultModel);
+ problems.setRootModel(resultModel);
+
+ // model path translation
+ resultModel = modelPathTranslator.alignToBaseDirectory(resultModel, resultModel.getProjectDirectory(), request);
+
+ // plugin management injection
+ resultModel = pluginManagementInjector.injectManagement(resultModel, request, problems);
+
+ fireEvent(resultModel, request, problems, ModelBuildingListener::buildExtensionsAssembled);
+
+ if (request.isProcessPlugins()) {
+ if (lifecycleBindingsInjector == null) {
+ throw new IllegalStateException("lifecycle bindings injector is missing");
+ }
+
+ // lifecycle bindings injection
+ resultModel = lifecycleBindingsInjector.injectLifecycleBindings(resultModel, request, problems);
+ }
+
+ // dependency management import
+ resultModel = importDependencyManagement(resultModel, request, problems, importIds);
+
+ // dependency management injection
+ resultModel = dependencyManagementInjector.injectManagement(resultModel, request, problems);
+
+ resultModel = modelNormalizer.injectDefaultValues(resultModel, request, problems);
+
+ if (request.isProcessPlugins()) {
+ // plugins configuration
+ resultModel = pluginConfigurationExpander.expandPluginConfiguration(resultModel, request, problems);
+ }
+
+ result.setEffectiveModel(resultModel);
+
+ // effective model validation
+ modelValidator.validateEffectiveModel(resultModel, request, problems);
+
+ if (hasModelErrors(problems)) {
+ throw problems.newModelBuilderException();
+ }
+
+ return result;
+ }
+
+ private DefaultModelBuilderResult asDefaultModelBuilderResult(ModelBuilderResult phaseOneResult) {
+ if (phaseOneResult instanceof DefaultModelBuilderResult) {
+ return (DefaultModelBuilderResult) phaseOneResult;
+ } else {
+ return new DefaultModelBuilderResult(phaseOneResult);
+ }
+ }
+
+ public Result extends Model> buildRawModel(Path pomFile, int validationLevel, boolean locationTracking) {
+ return buildRawModel(pomFile, validationLevel, locationTracking, null);
+ }
+
+ public Result extends Model> buildRawModel(
+ Path pomFile, int validationLevel, boolean locationTracking, TransformerContext context) {
+ final ModelBuilderRequest request = ModelBuilderRequest.builder()
+ .validationLevel(validationLevel)
+ .locationTracking(locationTracking)
+ .source(ModelSource.fromPath(pomFile))
+ .build();
+ DefaultModelProblemCollector problems = new DefaultModelProblemCollector(new DefaultModelBuilderResult());
+ try {
+ Model model = readFileModel(request, problems);
+
+ try {
+ if (transformer != null && context != null) {
+ transformer.transform(pomFile, context, model);
+ }
+ } catch (TransformerException e) {
+ problems.add(Severity.FATAL, ModelProblem.Version.V40, null, e);
+ }
+
+ return Result.newResult(model, problems.getProblems());
+ } catch (ModelBuilderException e) {
+ return Result.error(problems.getProblems());
+ }
+ }
+
+ Model readFileModel(ModelBuilderRequest request, DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ ModelSource modelSource = request.getSource();
+ Model model =
+ cache(getModelCache(request), modelSource, FILE, () -> doReadFileModel(modelSource, request, problems));
+
+ if (modelSource.getPath() != null) {
+ if (getTransformerContextBuilder(request) instanceof DefaultTransformerContextBuilder contextBuilder) {
+ contextBuilder.putSource(getGroupId(model), model.getArtifactId(), modelSource);
+ }
+ }
+
+ return model;
+ }
+
+ @SuppressWarnings("checkstyle:methodlength")
+ private Model doReadFileModel(
+ ModelSource modelSource, ModelBuilderRequest request, DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ Model model;
+ problems.setSource(modelSource.getLocation());
+ try {
+ boolean strict = request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0;
+
+ try (InputStream is = modelSource.openStream()) {
+ model = modelProcessor.read(XmlReaderRequest.builder()
+ .strict(strict)
+ .location(modelSource.getLocation())
+ .path(modelSource.getPath())
+ .inputStream(is)
+ .build());
+ } catch (XmlReaderException e) {
+ if (!strict) {
+ throw e;
+ }
+ try (InputStream is = modelSource.openStream()) {
+ model = modelProcessor.read(XmlReaderRequest.builder()
+ .strict(false)
+ .location(modelSource.getLocation())
+ .path(modelSource.getPath())
+ .inputStream(is)
+ .build());
+ } catch (XmlReaderException ne) {
+ // still unreadable even in non-strict mode, rethrow original error
+ throw e;
+ }
+
+ Severity severity = request.isProjectBuild() ? Severity.ERROR : Severity.WARNING;
+ problems.add(
+ severity,
+ ModelProblem.Version.V20,
+ "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage(),
+ e);
+ }
+
+ InputLocation loc = model.getLocation("");
+ InputSource v4src = loc != null ? loc.getSource() : null;
+ if (v4src != null) {
+ try {
+ Field field = InputSource.class.getDeclaredField("modelId");
+ field.setAccessible(true);
+ field.set(v4src, ModelProblemUtils.toId(model));
+ } catch (Throwable t) {
+ // TODO: use a lazy source ?
+ throw new IllegalStateException("Unable to set modelId on InputSource", t);
+ }
+ }
+ } catch (XmlReaderException e) {
+ problems.add(
+ Severity.FATAL,
+ ModelProblem.Version.BASE,
+ "Non-parseable POM " + modelSource.getLocation() + ": " + e.getMessage(),
+ e);
+ throw problems.newModelBuilderException();
+ } catch (IOException e) {
+ String msg = e.getMessage();
+ if (msg == null || msg.isEmpty()) {
+ // NOTE: There's java.nio.charset.MalformedInputException and sun.io.MalformedInputException
+ if (e.getClass().getName().endsWith("MalformedInputException")) {
+ msg = "Some input bytes do not match the file encoding.";
+ } else {
+ msg = e.getClass().getSimpleName();
+ }
+ }
+ problems.add(
+ Severity.FATAL,
+ ModelProblem.Version.BASE,
+ "Non-readable POM " + modelSource.getLocation() + ": " + msg,
+ e);
+ throw problems.newModelBuilderException();
+ }
+
+ if (modelSource.getPath() != null) {
+ model = model.withPomFile(modelSource.getPath());
+ }
+
+ problems.setSource(model);
+ modelValidator.validateFileModel(model, request, problems);
+ if (hasFatalErrors(problems)) {
+ throw problems.newModelBuilderException();
+ }
+
+ return model;
+ }
+
+ Model readRawModel(ModelBuilderRequest request, DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ ModelSource modelSource = request.getSource();
+
+ ModelData modelData =
+ cache(getModelCache(request), modelSource, RAW, () -> doReadRawModel(modelSource, request, problems));
+
+ return modelData.model();
+ }
+
+ private ModelData doReadRawModel(
+ ModelSource modelSource, ModelBuilderRequest request, DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ Model rawModel = readFileModel(request, problems);
+ if (Features.buildConsumer(request.getUserProperties()) && modelSource.getPath() != null) {
+ Path pomFile = modelSource.getPath();
+
+ try {
+ TransformerContextBuilder transformerContextBuilder = getTransformerContextBuilder(request);
+ if (transformerContextBuilder != null) {
+ TransformerContext context = transformerContextBuilder.initialize(request, problems);
+ this.transformer.transform(pomFile, context, rawModel);
+ }
+ } catch (TransformerException e) {
+ problems.add(Severity.FATAL, ModelProblem.Version.V40, null, e);
+ }
+ }
+
+ modelValidator.validateRawModel(rawModel, request, problems);
+
+ if (hasFatalErrors(problems)) {
+ throw problems.newModelBuilderException();
+ }
+
+ return new ModelData(modelSource, rawModel);
+ }
+
+ static String getGroupId(Model model) {
+ String groupId = model.getGroupId();
+ if (groupId == null && model.getParent() != null) {
+ groupId = model.getParent().getGroupId();
+ }
+ return groupId;
+ }
+
+ private String getVersion(Model model) {
+ String version = model.getVersion();
+ if (version == null && model.getParent() != null) {
+ version = model.getParent().getVersion();
+ }
+ return version;
+ }
+
+ private DefaultProfileActivationContext getProfileActivationContext(ModelBuilderRequest request, Model model) {
+ DefaultProfileActivationContext context = new DefaultProfileActivationContext();
+
+ context.setActiveProfileIds(request.getActiveProfileIds());
+ context.setInactiveProfileIds(request.getInactiveProfileIds());
+ context.setSystemProperties(request.getSystemProperties());
+ // enrich user properties with project packaging
+ Map userProperties = new HashMap<>(request.getUserProperties());
+ if (!userProperties.containsKey(ProfileActivationContext.PROPERTY_NAME_PACKAGING)) {
+ userProperties.put(ProfileActivationContext.PROPERTY_NAME_PACKAGING, model.getPackaging());
+ }
+ context.setUserProperties(userProperties);
+ context.setProjectDirectory(model.getProjectDirectory());
+
+ return context;
+ }
+
+ private void configureResolver(
+ ModelResolver modelResolver,
+ Model model,
+ ModelBuilderRequest request,
+ DefaultModelProblemCollector problems,
+ boolean replaceRepositories) {
+ model.getRepositories().forEach(r -> modelResolver.addRepository(request.getSession(), r, replaceRepositories));
+ }
+
+ private void checkPluginVersions(List lineage, ModelBuilderRequest request, ModelProblemCollector problems) {
+ if (request.getValidationLevel() < ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
+ return;
+ }
+
+ Map plugins = new HashMap<>();
+ Map versions = new HashMap<>();
+ Map managedVersions = new HashMap<>();
+
+ for (int i = lineage.size() - 1; i >= 0; i--) {
+ Model model = lineage.get(i);
+ Build build = model.getBuild();
+ if (build != null) {
+ for (Plugin plugin : build.getPlugins()) {
+ String key = plugin.getKey();
+ if (versions.get(key) == null) {
+ versions.put(key, plugin.getVersion());
+ plugins.put(key, plugin);
+ }
+ }
+ PluginManagement mgmt = build.getPluginManagement();
+ if (mgmt != null) {
+ for (Plugin plugin : mgmt.getPlugins()) {
+ String key = plugin.getKey();
+ managedVersions.computeIfAbsent(key, k -> plugin.getVersion());
+ }
+ }
+ }
+ }
+
+ for (String key : versions.keySet()) {
+ if (versions.get(key) == null && managedVersions.get(key) == null) {
+ InputLocation location = plugins.get(key).getLocation("");
+ problems.add(
+ Severity.WARNING,
+ ModelProblem.Version.V20,
+ "'build.plugins.plugin.version' for " + key + " is missing.",
+ location);
+ }
+ }
+ }
+
+ private Model assembleInheritance(
+ List lineage, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model parent = lineage.get(lineage.size() - 1);
+ for (int i = lineage.size() - 2; i >= 0; i--) {
+ Model child = lineage.get(i);
+ parent = inheritanceAssembler.assembleModelInheritance(child, parent, request, problems);
+ }
+ return parent;
+ }
+
+ private Map getProfileActivations(Model model) {
+ return model.getProfiles().stream()
+ .filter(p -> p.getActivation() != null)
+ .collect(Collectors.toMap(Profile::getId, Profile::getActivation));
+ }
+
+ private Model injectProfileActivations(Model model, Map activations) {
+ List profiles = new ArrayList<>();
+ boolean modified = false;
+ for (Profile profile : model.getProfiles()) {
+ Activation activation = profile.getActivation();
+ if (activation != null) {
+ // restore activation
+ profile = profile.withActivation(activations.get(profile.getId()));
+ modified = true;
+ }
+ profiles.add(profile);
+ }
+ return modified ? model.withProfiles(profiles) : model;
+ }
+
+ private Model interpolateModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model interpolatedModel =
+ modelInterpolator.interpolateModel(model, model.getProjectDirectory(), request, problems);
+ if (interpolatedModel.getParent() != null) {
+ StringSearchInterpolator ssi = new StringSearchInterpolator();
+ ssi.addValueSource(new MapBasedValueSource(request.getSession().getUserProperties()));
+ ssi.addValueSource(new MapBasedValueSource(model.getProperties()));
+ ssi.addValueSource(new MapBasedValueSource(request.getSession().getSystemProperties()));
+ try {
+ String interpolated =
+ ssi.interpolate(interpolatedModel.getParent().getVersion());
+ interpolatedModel = interpolatedModel.withParent(
+ interpolatedModel.getParent().withVersion(interpolated));
+ } catch (Exception e) {
+ problems.add(
+ Severity.ERROR,
+ ModelProblem.Version.BASE,
+ "Failed to interpolate field: "
+ + interpolatedModel.getParent().getVersion()
+ + " on class: ",
+ e);
+ }
+ }
+ interpolatedModel = interpolatedModel.withPomFile(model.getPomFile());
+ return interpolatedModel;
+ }
+
+ private ModelData readParent(
+ Model childModel,
+ ModelSource childSource,
+ ModelBuilderRequest request,
+ DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ ModelData parentData = null;
+
+ Parent parent = childModel.getParent();
+ if (parent != null) {
+ parentData = readParentLocally(childModel, childSource, request, problems);
+ if (parentData == null) {
+ parentData = readParentExternally(childModel, request, problems);
+ }
+
+ Model parentModel = parentData.model();
+ if (!"pom".equals(parentModel.getPackaging())) {
+ problems.add(
+ Severity.ERROR,
+ ModelProblem.Version.BASE,
+ "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint(parentModel)
+ + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"",
+ parentModel.getLocation("packaging"));
+ }
+ }
+
+ return parentData;
+ }
+
+ private ModelData readParentLocally(
+ Model childModel,
+ ModelSource childSource,
+ ModelBuilderRequest request,
+ DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ final Parent parent = childModel.getParent();
+ final ModelSource candidateSource;
+ final Model candidateModel;
+ final WorkspaceModelResolver resolver = getWorkspaceModelResolver(request);
+ if (resolver == null) {
+ candidateSource = getParentPomFile(childModel, childSource);
+
+ if (candidateSource == null) {
+ return null;
+ }
+
+ ModelBuilderRequest candidateBuildRequest = ModelBuilderRequest.build(request, candidateSource);
+
+ candidateModel = readRawModel(candidateBuildRequest, problems);
+ } else {
+ try {
+ candidateModel =
+ resolver.resolveRawModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
+ } catch (ModelBuilderException e) {
+ problems.add(Severity.FATAL, ModelProblem.Version.BASE, e.getMessage(), parent.getLocation(""), e);
+ throw problems.newModelBuilderException();
+ }
+ if (candidateModel == null) {
+ return null;
+ }
+ candidateSource = ModelSource.fromPath(candidateModel.getPomFile());
+ }
+
+ //
+ // TODO jvz Why isn't all this checking the job of the duty of the workspace resolver, we know that we
+ // have a model that is suitable, yet more checks are done here and the one for the version is problematic
+ // before because with parents as ranges it will never work in this scenario.
+ //
+
+ String groupId = getGroupId(candidateModel);
+ String artifactId = candidateModel.getArtifactId();
+
+ if (groupId == null
+ || !groupId.equals(parent.getGroupId())
+ || artifactId == null
+ || !artifactId.equals(parent.getArtifactId())) {
+ StringBuilder buffer = new StringBuilder(256);
+ buffer.append("'parent.relativePath'");
+ if (childModel != problems.getRootModel()) {
+ buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel));
+ }
+ buffer.append(" points at ").append(groupId).append(':').append(artifactId);
+ buffer.append(" instead of ").append(parent.getGroupId()).append(':');
+ buffer.append(parent.getArtifactId()).append(", please verify your project structure");
+
+ problems.setSource(childModel);
+ problems.add(Severity.WARNING, ModelProblem.Version.BASE, buffer.toString(), parent.getLocation(""));
+ return null;
+ }
+
+ String version = getVersion(candidateModel);
+ if (version != null && parent.getVersion() != null && !version.equals(parent.getVersion())) {
+ try {
+ VersionRange parentRange = versionParser.parseVersionRange(parent.getVersion());
+ if (!parentRange.contains(versionParser.parseVersion(version))) {
+ // version skew drop back to resolution from the repository
+ return null;
+ }
+
+ // Validate versions aren't inherited when using parent ranges the same way as when read externally.
+ String rawChildModelVersion = childModel.getVersion();
+
+ if (rawChildModelVersion == null) {
+ // Message below is checked for in the MNG-2199 core IT.
+ problems.add(
+ Severity.FATAL,
+ ModelProblem.Version.V31,
+ "Version must be a constant",
+ childModel.getLocation(""));
+
+ } else {
+ if (rawChildVersionReferencesParent(rawChildModelVersion)) {
+ // Message below is checked for in the MNG-2199 core IT.
+ problems.add(
+ Severity.FATAL,
+ ModelProblem.Version.V31,
+ "Version must be a constant",
+ childModel.getLocation("version"));
+ }
+ }
+
+ // MNG-2199: What else to check here ?
+ } catch (VersionParserException e) {
+ // invalid version range, so drop back to resolution from the repository
+ return null;
+ }
+ }
+
+ //
+ // Here we just need to know that a version is fine to use but this validation we can do in our workspace
+ // resolver.
+ //
+
+ /*
+ * if ( version == null || !version.equals( parent.getVersion() ) ) { return null; }
+ */
+
+ return new ModelData(candidateSource, candidateModel);
+ }
+
+ private boolean rawChildVersionReferencesParent(String rawChildModelVersion) {
+ return rawChildModelVersion.equals("${pom.version}")
+ || rawChildModelVersion.equals("${project.version}")
+ || rawChildModelVersion.equals("${pom.parent.version}")
+ || rawChildModelVersion.equals("${project.parent.version}");
+ }
+
+ private ModelSource getParentPomFile(Model childModel, ModelSource source) {
+ String parentPath = childModel.getParent().getRelativePath();
+ if (parentPath == null || parentPath.isEmpty()) {
+ return null;
+ } else {
+ return source.resolve(modelProcessor::locateExistingPom, parentPath);
+ }
+ }
+
+ private ModelData readParentExternally(
+ Model childModel, ModelBuilderRequest request, DefaultModelProblemCollector problems)
+ throws ModelBuilderException {
+ problems.setSource(childModel);
+
+ Parent parent = childModel.getParent();
+
+ String groupId = parent.getGroupId();
+ String artifactId = parent.getArtifactId();
+ String version = parent.getVersion();
+
+ ModelResolver modelResolver = getModelResolver(request);
+ Objects.requireNonNull(
+ modelResolver,
+ String.format(
+ "request.modelResolver cannot be null (parent POM %s and POM %s)",
+ ModelProblemUtils.toId(groupId, artifactId, version),
+ ModelProblemUtils.toSourceHint(childModel)));
+
+ ModelSource modelSource;
+ try {
+ modelSource = modelResolver.resolveModel(request.getSession(), parent, new AtomicReference<>());
+ } catch (ModelBuilderException e) {
+ // Message below is checked for in the MNG-2199 core IT.
+ StringBuilder buffer = new StringBuilder(256);
+ buffer.append("Non-resolvable parent POM");
+ if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
+ buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
+ }
+ if (childModel != problems.getRootModel()) {
+ buffer.append(" for ").append(ModelProblemUtils.toId(childModel));
+ }
+ buffer.append(": ").append(e.getMessage());
+ if (childModel.getProjectDirectory() != null) {
+ if (parent.getRelativePath() == null || parent.getRelativePath().isEmpty()) {
+ buffer.append(" and 'parent.relativePath' points at no local POM");
+ } else {
+ buffer.append(" and 'parent.relativePath' points at wrong local POM");
+ }
+ }
+
+ problems.add(Severity.FATAL, ModelProblem.Version.BASE, buffer.toString(), parent.getLocation(""), e);
+ throw problems.newModelBuilderException();
+ }
+
+ int validationLevel = Math.min(request.getValidationLevel(), ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0);
+ ModelBuilderRequest lenientRequest = ModelBuilderRequest.builder(request)
+ .validationLevel(validationLevel)
+ .source(modelSource)
+ .build();
+
+ Model parentModel = readRawModel(lenientRequest, problems);
+
+ if (!parent.getVersion().equals(version)) {
+ String rawChildModelVersion = childModel.getVersion();
+
+ if (rawChildModelVersion == null) {
+ // Message below is checked for in the MNG-2199 core IT.
+ problems.add(
+ Severity.FATAL,
+ ModelProblem.Version.V31,
+ "Version must be a constant",
+ childModel.getLocation(""));
+
+ } else {
+ if (rawChildVersionReferencesParent(rawChildModelVersion)) {
+ // Message below is checked for in the MNG-2199 core IT.
+ problems.add(
+ Severity.FATAL,
+ ModelProblem.Version.V31,
+ "Version must be a constant",
+ childModel.getLocation("version"));
+ }
+ }
+
+ // MNG-2199: What else to check here ?
+ }
+
+ return new ModelData(modelSource, parentModel);
+ }
+
+ private Model getSuperModel(String modelVersion) {
+ return superPomProvider.getSuperPom(modelVersion);
+ }
+
+ private Model importDependencyManagement(
+ Model model,
+ ModelBuilderRequest request,
+ DefaultModelProblemCollector problems,
+ Collection importIds) {
+ DependencyManagement depMgmt = model.getDependencyManagement();
+
+ if (depMgmt == null) {
+ return model;
+ }
+
+ String importing = model.getGroupId() + ':' + model.getArtifactId() + ':' + model.getVersion();
+
+ importIds.add(importing);
+
+ List importMgmts = null;
+
+ for (Iterator it = depMgmt.getDependencies().iterator(); it.hasNext(); ) {
+ Dependency dependency = it.next();
+
+ if (!("pom".equals(dependency.getType()) && "import".equals(dependency.getScope()))
+ || "bom".equals(dependency.getType())) {
+ continue;
+ }
+
+ // TODO: what is it used for ?
+ // it.remove();
+
+ DependencyManagement importMgmt = loadDependencyManagement(model, request, problems, dependency, importIds);
+
+ if (importMgmt != null) {
+ if (importMgmts == null) {
+ importMgmts = new ArrayList<>();
+ }
+
+ importMgmts.add(importMgmt);
+ }
+ }
+
+ importIds.remove(importing);
+
+ return dependencyManagementImporter.importManagement(model, importMgmts, request, problems);
+ }
+
+ private DependencyManagement loadDependencyManagement(
+ Model model,
+ ModelBuilderRequest request,
+ DefaultModelProblemCollector problems,
+ Dependency dependency,
+ Collection importIds) {
+ String groupId = dependency.getGroupId();
+ String artifactId = dependency.getArtifactId();
+ String version = dependency.getVersion();
+
+ if (groupId == null || groupId.isEmpty()) {
+ problems.add(
+ Severity.ERROR,
+ ModelProblem.Version.BASE,
+ "'dependencyManagement.dependencies.dependency.groupId' for " + dependency.getManagementKey()
+ + " is missing.",
+ dependency.getLocation(""));
+ return null;
+ }
+ if (artifactId == null || artifactId.isEmpty()) {
+ problems.add(
+ Severity.ERROR,
+ ModelProblem.Version.BASE,
+ "'dependencyManagement.dependencies.dependency.artifactId' for " + dependency.getManagementKey()
+ + " is missing.",
+ dependency.getLocation(""));
+ return null;
+ }
+ if (version == null || version.isEmpty()) {
+ problems.add(
+ Severity.ERROR,
+ ModelProblem.Version.BASE,
+ "'dependencyManagement.dependencies.dependency.version' for " + dependency.getManagementKey()
+ + " is missing.",
+ dependency.getLocation(""));
+ return null;
+ }
+
+ String imported = groupId + ':' + artifactId + ':' + version;
+
+ if (importIds.contains(imported)) {
+ StringBuilder message =
+ new StringBuilder("The dependencies of type=pom and with scope=import form a cycle: ");
+ for (String modelId : importIds) {
+ message.append(modelId).append(" -> ");
+ }
+ message.append(imported);
+ problems.add(Severity.ERROR, ModelProblem.Version.BASE, message.toString());
+
+ return null;
+ }
+
+ DependencyManagement importMgmt = cache(
+ getModelCache(request),
+ groupId,
+ artifactId,
+ version,
+ IMPORT,
+ () -> doLoadDependencyManagement(
+ model, request, problems, dependency, groupId, artifactId, version, importIds));
+
+ // [MNG-5600] Dependency management import should support exclusions.
+ List exclusions = dependency.getExclusions();
+ if (importMgmt != null && !exclusions.isEmpty()) {
+ // Dependency excluded from import.
+ List dependencies = importMgmt.getDependencies().stream()
+ .filter(candidate -> exclusions.stream().noneMatch(exclusion -> match(exclusion, candidate)))
+ .map(candidate -> candidate.withExclusions(exclusions))
+ .collect(Collectors.toList());
+ importMgmt = importMgmt.withDependencies(dependencies);
+ }
+
+ return importMgmt;
+ }
+
+ private boolean match(Exclusion exclusion, Dependency candidate) {
+ return match(exclusion.getGroupId(), candidate.getGroupId())
+ && match(exclusion.getArtifactId(), candidate.getArtifactId());
+ }
+
+ private boolean match(String match, String text) {
+ return match.equals("*") || match.equals(text);
+ }
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ private DependencyManagement doLoadDependencyManagement(
+ Model model,
+ ModelBuilderRequest request,
+ DefaultModelProblemCollector problems,
+ Dependency dependency,
+ String groupId,
+ String artifactId,
+ String version,
+ Collection importIds) {
+ DependencyManagement importMgmt;
+ final WorkspaceModelResolver workspaceResolver = getWorkspaceModelResolver(request);
+ final ModelResolver modelResolver = getModelResolver(request);
+ if (workspaceResolver == null && modelResolver == null) {
+ throw new NullPointerException(String.format(
+ "request.workspaceModelResolver and request.modelResolver cannot be null (parent POM %s and POM %s)",
+ ModelProblemUtils.toId(groupId, artifactId, version), ModelProblemUtils.toSourceHint(model)));
+ }
+
+ Model importModel = null;
+ if (workspaceResolver != null) {
+ try {
+ importModel = workspaceResolver.resolveEffectiveModel(groupId, artifactId, version);
+ } catch (ModelBuilderException e) {
+ problems.add(Severity.FATAL, ModelProblem.Version.BASE, null, e);
+ return null;
+ }
+ }
+
+ // no workspace resolver or workspace resolver returned null (i.e. model not in workspace)
+ if (importModel == null) {
+ final ModelSource importSource;
+ try {
+ importSource = modelResolver.resolveModel(request.getSession(), dependency, new AtomicReference<>());
+ } catch (ModelBuilderException e) {
+ StringBuilder buffer = new StringBuilder(256);
+ buffer.append("Non-resolvable import POM");
+ if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
+ buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
+ }
+ buffer.append(": ").append(e.getMessage());
+
+ problems.add(
+ Severity.ERROR, ModelProblem.Version.BASE, buffer.toString(), dependency.getLocation(""), e);
+ return null;
+ }
+
+ if (importSource.getPath() != null && request.getSession().getRootDirectory() != null) {
+ Path sourcePath = importSource.getPath();
+ if (sourcePath.startsWith(request.getSession().getRootDirectory())) {
+ problems.add(
+ Severity.WARNING,
+ ModelProblem.Version.BASE,
+ "BOM imports from within reactor should be avoided",
+ dependency.getLocation(""));
+ }
+ }
+
+ final ModelBuilderResult importResult;
+ try {
+ ModelBuilderRequest importRequest = ModelBuilderRequest.builder(request)
+ .validationLevel(ModelBuilderRequest.VALIDATION_LEVEL_MINIMAL)
+ .source(importSource)
+ .modelResolver(modelResolver.newCopy())
+ .build();
+ importResult = build(importRequest, importIds);
+ } catch (ModelBuilderException e) {
+ e.getResult().getProblems().forEach(problems::add);
+ return null;
+ }
+
+ importResult.getProblems().forEach(problems::add);
+
+ importModel = importResult.getEffectiveModel();
+ }
+
+ importMgmt = importModel.getDependencyManagement();
+
+ if (importMgmt == null) {
+ importMgmt = DependencyManagement.newInstance();
+ }
+ return importMgmt;
+ }
+
+ private static T cache(
+ ModelCache cache, String groupId, String artifactId, String version, String tag, Callable supplier) {
+ Supplier s = asSupplier(supplier);
+ if (cache == null) {
+ return s.get();
+ } else {
+ return cache.computeIfAbsent(groupId, artifactId, version, tag, s);
+ }
+ }
+
+ private static T cache(ModelCache cache, Source source, String tag, Callable supplier) {
+ Supplier s = asSupplier(supplier);
+ if (cache == null) {
+ return s.get();
+ } else {
+ return cache.computeIfAbsent(source, tag, s);
+ }
+ }
+
+ private static Supplier asSupplier(Callable supplier) {
+ return () -> {
+ try {
+ return supplier.call();
+ } catch (Exception e) {
+ uncheckedThrow(e);
+ return null;
+ }
+ };
+ }
+
+ static void uncheckedThrow(Throwable t) throws T {
+ throw (T) t; // rely on vacuous cast
+ }
+
+ private void fireEvent(
+ Model model,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems,
+ BiConsumer catapult) {
+ ModelBuildingListener listener = getModelBuildingListener(request);
+
+ if (listener != null) {
+ ModelBuildingEvent event = new DefaultModelBuildingEvent(model, request, problems);
+
+ catapult.accept(listener, event);
+ }
+ }
+
+ private boolean containsCoordinates(String message, String groupId, String artifactId, String version) {
+ return message != null
+ && (groupId == null || message.contains(groupId))
+ && (artifactId == null || message.contains(artifactId))
+ && (version == null || message.contains(version));
+ }
+
+ protected boolean hasModelErrors(ModelProblemCollector problems) {
+ if (problems instanceof DefaultModelProblemCollector) {
+ return ((DefaultModelProblemCollector) problems).hasErrors();
+ } else {
+ // the default execution path only knows the DefaultModelProblemCollector,
+ // only reason it's not in signature is because it's package private
+ throw new IllegalStateException();
+ }
+ }
+
+ protected boolean hasFatalErrors(ModelProblemCollector problems) {
+ if (problems instanceof DefaultModelProblemCollector) {
+ return ((DefaultModelProblemCollector) problems).hasFatalErrors();
+ } else {
+ // the default execution path only knows the DefaultModelProblemCollector,
+ // only reason it's not in signature is because it's package private
+ throw new IllegalStateException();
+ }
+ }
+
+ ModelProcessor getModelProcessor() {
+ return modelProcessor;
+ }
+
+ private static ModelCache getModelCache(ModelBuilderRequest request) {
+ return request.getModelCache();
+ }
+
+ private static ModelBuildingListener getModelBuildingListener(ModelBuilderRequest request) {
+ return null; // request.getModelBuildingListener();
+ }
+
+ private static WorkspaceModelResolver getWorkspaceModelResolver(ModelBuilderRequest request) {
+ return null; // request.getWorkspaceModelResolver();
+ }
+
+ private static ModelResolver getModelResolver(ModelBuilderRequest request) {
+ return request.getModelResolver();
+ }
+
+ private static TransformerContextBuilder getTransformerContextBuilder(ModelBuilderRequest request) {
+ return null;
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilderResult.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilderResult.java
new file mode 100644
index 000000000000..0013db500c01
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilderResult.java
@@ -0,0 +1,213 @@
+/*
+ * 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.internal.impl.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Profile;
+import org.apache.maven.api.services.ModelBuilderResult;
+import org.apache.maven.api.services.ModelProblem;
+
+/**
+ * Collects the output of the model builder.
+ *
+ */
+class DefaultModelBuilderResult implements ModelBuilderResult {
+ private Model fileModel;
+
+ private Model effectiveModel;
+
+ private List modelIds;
+
+ private Map rawModels;
+
+ private Map> activePomProfiles;
+
+ private List activeExternalProfiles;
+
+ private List problems;
+
+ DefaultModelBuilderResult() {
+ modelIds = new ArrayList<>();
+ rawModels = new HashMap<>();
+ activePomProfiles = new HashMap<>();
+ activeExternalProfiles = new ArrayList<>();
+ problems = new ArrayList<>();
+ }
+
+ DefaultModelBuilderResult(ModelBuilderResult result) {
+ this();
+ this.activeExternalProfiles.addAll(result.getActiveExternalProfiles());
+ this.effectiveModel = result.getEffectiveModel();
+ this.fileModel = result.getFileModel();
+ this.problems.addAll(result.getProblems());
+
+ for (String modelId : result.getModelIds()) {
+ this.modelIds.add(modelId);
+ this.rawModels.put(modelId, result.getRawModel(modelId).orElseThrow());
+ this.activePomProfiles.put(modelId, result.getActivePomProfiles(modelId));
+ }
+ }
+
+ @Override
+ public Model getFileModel() {
+ return fileModel;
+ }
+
+ public DefaultModelBuilderResult setFileModel(Model fileModel) {
+ this.fileModel = fileModel;
+
+ return this;
+ }
+
+ @Override
+ public Model getEffectiveModel() {
+ return effectiveModel;
+ }
+
+ public DefaultModelBuilderResult setEffectiveModel(Model model) {
+ this.effectiveModel = model;
+
+ return this;
+ }
+
+ @Override
+ public List getModelIds() {
+ return modelIds;
+ }
+
+ public DefaultModelBuilderResult addModelId(String modelId) {
+ // Intentionally notNull because Super POM may not contain a modelId
+ Objects.requireNonNull(modelId, "modelId cannot null");
+
+ modelIds.add(modelId);
+
+ return this;
+ }
+
+ @Override
+ public Model getRawModel() {
+ return rawModels.get(modelIds.get(0));
+ }
+
+ @Override
+ public Optional getRawModel(String modelId) {
+ return Optional.ofNullable(rawModels.get(modelId));
+ }
+
+ public DefaultModelBuilderResult setRawModel(String modelId, Model rawModel) {
+ // Intentionally notNull because Super POM may not contain a modelId
+ Objects.requireNonNull(modelId, "modelId cannot null");
+
+ rawModels.put(modelId, rawModel);
+
+ return this;
+ }
+
+ @Override
+ public List getActivePomProfiles(String modelId) {
+ return activePomProfiles.get(modelId);
+ }
+
+ public DefaultModelBuilderResult setActivePomProfiles(String modelId, List activeProfiles) {
+ // Intentionally notNull because Super POM may not contain a modelId
+ Objects.requireNonNull(modelId, "modelId cannot null");
+
+ if (activeProfiles != null) {
+ this.activePomProfiles.put(modelId, new ArrayList<>(activeProfiles));
+ } else {
+ this.activePomProfiles.remove(modelId);
+ }
+
+ return this;
+ }
+
+ @Override
+ public List getActiveExternalProfiles() {
+ return activeExternalProfiles;
+ }
+
+ public DefaultModelBuilderResult setActiveExternalProfiles(List activeProfiles) {
+ if (activeProfiles != null) {
+ this.activeExternalProfiles = new ArrayList<>(activeProfiles);
+ } else {
+ this.activeExternalProfiles.clear();
+ }
+
+ return this;
+ }
+
+ @Override
+ public List getProblems() {
+ return problems;
+ }
+
+ public DefaultModelBuilderResult setProblems(List problems) {
+ if (problems != null) {
+ this.problems = new ArrayList<>(problems);
+ } else {
+ this.problems.clear();
+ }
+
+ return this;
+ }
+
+ public String toString() {
+ if (modelIds.isEmpty()) {
+ String modelId = modelIds.get(0);
+ StringBuilder sb = new StringBuilder();
+ sb.append(problems.size())
+ .append(
+ (problems.size() == 1)
+ ? " problem was "
+ : " problems were encountered while building the effective model");
+ if (modelId != null && !modelId.isEmpty()) {
+ sb.append(" for ");
+ sb.append(modelId);
+ }
+ for (ModelProblem problem : problems) {
+ sb.append(System.lineSeparator());
+ sb.append(" - [");
+ sb.append(problem.getSeverity());
+ sb.append("] ");
+ sb.append(problem.getMessage());
+ String loc = Stream.of(
+ problem.getModelId().equals(modelId) ? problem.getModelId() : "",
+ problem.getModelId().equals(modelId) ? problem.getSource() : "",
+ problem.getLineNumber() > 0 ? "line " + problem.getLineNumber() : "",
+ problem.getColumnNumber() > 0 ? "column " + problem.getColumnNumber() : "")
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.joining(", "));
+ if (!loc.isEmpty()) {
+ sb.append(" @ ").append(loc);
+ }
+ }
+ return sb.toString();
+ }
+ return null;
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuildingEvent.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuildingEvent.java
new file mode 100644
index 000000000000..eba7457c4191
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuildingEvent.java
@@ -0,0 +1,30 @@
+/*
+ * 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.internal.impl.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.model.ModelBuildingEvent;
+import org.apache.maven.api.services.model.ModelProblemCollector;
+
+/**
+ * Holds data relevant for a model building event.
+ */
+record DefaultModelBuildingEvent(Model model, ModelBuilderRequest request, ModelProblemCollector problems)
+ implements ModelBuildingEvent {}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelInterpolator.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelInterpolator.java
new file mode 100644
index 000000000000..daad44f5059d
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelInterpolator.java
@@ -0,0 +1,484 @@
+/*
+ * 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.internal.impl.model;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.BuilderProblem;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.ModelProblem;
+import org.apache.maven.api.services.model.*;
+import org.apache.maven.model.v4.MavenTransformer;
+import org.codehaus.plexus.interpolation.AbstractDelegatingValueSource;
+import org.codehaus.plexus.interpolation.AbstractValueSource;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
+import org.codehaus.plexus.interpolation.MapBasedValueSource;
+import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
+import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
+import org.codehaus.plexus.interpolation.QueryEnabledValueSource;
+import org.codehaus.plexus.interpolation.RecursionInterceptor;
+import org.codehaus.plexus.interpolation.StringSearchInterpolator;
+import org.codehaus.plexus.interpolation.ValueSource;
+import org.codehaus.plexus.interpolation.reflection.ReflectionValueExtractor;
+import org.codehaus.plexus.interpolation.util.ValueSourceUtils;
+
+@Named
+@Singleton
+public class DefaultModelInterpolator implements ModelInterpolator {
+
+ private static final String PREFIX_PROJECT = "project.";
+ private static final String PREFIX_POM = "pom.";
+ private static final List PROJECT_PREFIXES_3_1 = Arrays.asList(PREFIX_POM, PREFIX_PROJECT);
+ private static final List PROJECT_PREFIXES_4_0 = Collections.singletonList(PREFIX_PROJECT);
+
+ private static final Collection TRANSLATED_PATH_EXPRESSIONS;
+
+ static {
+ Collection translatedPrefixes = new HashSet<>();
+
+ // MNG-1927, MNG-2124, MNG-3355:
+ // If the build section is present and the project directory is non-null, we should make
+ // sure interpolation of the directories below uses translated paths.
+ // Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
+ // code below...
+ translatedPrefixes.add("build.directory");
+ translatedPrefixes.add("build.outputDirectory");
+ translatedPrefixes.add("build.testOutputDirectory");
+ translatedPrefixes.add("build.sourceDirectory");
+ translatedPrefixes.add("build.testSourceDirectory");
+ translatedPrefixes.add("build.scriptSourceDirectory");
+ translatedPrefixes.add("reporting.outputDirectory");
+
+ TRANSLATED_PATH_EXPRESSIONS = translatedPrefixes;
+ }
+
+ private final PathTranslator pathTranslator;
+ private final UrlNormalizer urlNormalizer;
+ private final RootLocator rootLocator;
+
+ @Inject
+ public DefaultModelInterpolator(
+ PathTranslator pathTranslator, UrlNormalizer urlNormalizer, RootLocator rootLocator) {
+ this.pathTranslator = pathTranslator;
+ this.urlNormalizer = urlNormalizer;
+ this.rootLocator = rootLocator;
+ }
+
+ interface InnerInterpolator {
+ String interpolate(String value);
+ }
+
+ @Override
+ public Model interpolateModel(
+ Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
+ List extends ValueSource> valueSources = createValueSources(model, projectDir, request, problems);
+ List extends InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, request);
+
+ InnerInterpolator innerInterpolator = createInterpolator(valueSources, postProcessors, request, problems);
+
+ return new MavenTransformer(innerInterpolator::interpolate).visit(model);
+ }
+
+ private InnerInterpolator createInterpolator(
+ List extends ValueSource> valueSources,
+ List extends InterpolationPostProcessor> postProcessors,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ Map cache = new HashMap<>();
+ StringSearchInterpolator interpolator = new StringSearchInterpolator();
+ interpolator.setCacheAnswers(true);
+ for (ValueSource vs : valueSources) {
+ interpolator.addValueSource(vs);
+ }
+ for (InterpolationPostProcessor postProcessor : postProcessors) {
+ interpolator.addPostProcessor(postProcessor);
+ }
+ RecursionInterceptor recursionInterceptor = createRecursionInterceptor(request);
+ return value -> {
+ if (value != null && value.contains("${")) {
+ String c = cache.get(value);
+ if (c == null) {
+ try {
+ c = interpolator.interpolate(value, recursionInterceptor);
+ } catch (InterpolationException e) {
+ problems.add(BuilderProblem.Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
+ }
+ cache.put(value, c);
+ }
+ return c;
+ }
+ return value;
+ };
+ }
+
+ protected List getProjectPrefixes(ModelBuilderRequest request) {
+ return request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_4_0
+ ? PROJECT_PREFIXES_4_0
+ : PROJECT_PREFIXES_3_1;
+ }
+
+ protected List createValueSources(
+ Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Map modelProperties = model.getProperties();
+
+ ValueSource projectPrefixValueSource;
+ ValueSource prefixlessObjectBasedValueSource;
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_4_0) {
+ projectPrefixValueSource = new PrefixedObjectValueSource(PROJECT_PREFIXES_4_0, model, false);
+ prefixlessObjectBasedValueSource = new ObjectBasedValueSource(model);
+ } else {
+ projectPrefixValueSource = new PrefixedObjectValueSource(PROJECT_PREFIXES_3_1, model, false);
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
+ projectPrefixValueSource =
+ new ProblemDetectingValueSource(projectPrefixValueSource, PREFIX_POM, PREFIX_PROJECT, problems);
+ }
+
+ prefixlessObjectBasedValueSource = new ObjectBasedValueSource(model);
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
+ prefixlessObjectBasedValueSource =
+ new ProblemDetectingValueSource(prefixlessObjectBasedValueSource, "", PREFIX_PROJECT, problems);
+ }
+ }
+
+ // NOTE: Order counts here!
+ List valueSources = new ArrayList<>(9);
+
+ if (projectDir != null) {
+ ValueSource basedirValueSource = new PrefixedValueSourceWrapper(
+ new AbstractValueSource(false) {
+ @Override
+ public Object getValue(String expression) {
+ if ("basedir".equals(expression)) {
+ return projectDir.toAbsolutePath().toString();
+ } else if (expression.startsWith("basedir.")) {
+ Path basedir = projectDir.toAbsolutePath();
+ return new ObjectBasedValueSource(basedir)
+ .getValue(expression.substring("basedir.".length()));
+ }
+ return null;
+ }
+ },
+ getProjectPrefixes(request),
+ true);
+ valueSources.add(basedirValueSource);
+
+ ValueSource baseUriValueSource = new PrefixedValueSourceWrapper(
+ new AbstractValueSource(false) {
+ @Override
+ public Object getValue(String expression) {
+ if ("baseUri".equals(expression)) {
+ return projectDir.toAbsolutePath().toUri().toASCIIString();
+ } else if (expression.startsWith("baseUri.")) {
+ URI baseUri = projectDir.toAbsolutePath().toUri();
+ return new ObjectBasedValueSource(baseUri)
+ .getValue(expression.substring("baseUri.".length()));
+ }
+ return null;
+ }
+ },
+ getProjectPrefixes(request),
+ false);
+ valueSources.add(baseUriValueSource);
+ valueSources.add(new BuildTimestampValueSource(request.getSession().getStartTime(), modelProperties));
+ }
+
+ valueSources.add(new PrefixedValueSourceWrapper(
+ new AbstractValueSource(false) {
+ @Override
+ public Object getValue(String expression) {
+ if ("rootDirectory".equals(expression)) {
+ Path root = rootLocator.findMandatoryRoot(projectDir);
+ return root.toFile().getPath();
+ } else if (expression.startsWith("rootDirectory.")) {
+ Path root = rootLocator.findMandatoryRoot(projectDir);
+ return new ObjectBasedValueSource(root)
+ .getValue(expression.substring("rootDirectory.".length()));
+ }
+ return null;
+ }
+ },
+ getProjectPrefixes(request)));
+
+ valueSources.add(projectPrefixValueSource);
+
+ valueSources.add(new MapBasedValueSource(request.getUserProperties()));
+
+ valueSources.add(new MapBasedValueSource(modelProperties));
+
+ valueSources.add(new MapBasedValueSource(request.getSystemProperties()));
+
+ valueSources.add(new AbstractValueSource(false) {
+ @Override
+ public Object getValue(String expression) {
+ return request.getSystemProperties().get("env." + expression);
+ }
+ });
+
+ valueSources.add(prefixlessObjectBasedValueSource);
+
+ return valueSources;
+ }
+
+ protected List extends InterpolationPostProcessor> createPostProcessors(
+ Model model, Path projectDir, ModelBuilderRequest request) {
+ List processors = new ArrayList<>(2);
+ if (projectDir != null) {
+ processors.add(new PathTranslatingPostProcessor(
+ getProjectPrefixes(request), TRANSLATED_PATH_EXPRESSIONS, projectDir, pathTranslator));
+ }
+ processors.add(new UrlNormalizingPostProcessor(urlNormalizer));
+ return processors;
+ }
+
+ protected RecursionInterceptor createRecursionInterceptor(ModelBuilderRequest request) {
+ return new PrefixAwareRecursionInterceptor(getProjectPrefixes(request));
+ }
+
+ static class PathTranslatingPostProcessor implements InterpolationPostProcessor {
+
+ private final Collection unprefixedPathKeys;
+ private final Path projectDir;
+ private final PathTranslator pathTranslator;
+ private final List expressionPrefixes;
+
+ PathTranslatingPostProcessor(
+ List expressionPrefixes,
+ Collection unprefixedPathKeys,
+ Path projectDir,
+ PathTranslator pathTranslator) {
+ this.expressionPrefixes = expressionPrefixes;
+ this.unprefixedPathKeys = unprefixedPathKeys;
+ this.projectDir = projectDir;
+ this.pathTranslator = pathTranslator;
+ }
+
+ @Override
+ public Object execute(String expression, Object value) {
+ if (value != null) {
+ expression = ValueSourceUtils.trimPrefix(expression, expressionPrefixes, true);
+ if (unprefixedPathKeys.contains(expression)) {
+ return pathTranslator.alignToBaseDirectory(String.valueOf(value), projectDir);
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Ensures that expressions referring to URLs evaluate to normalized URLs.
+ *
+ */
+ static class UrlNormalizingPostProcessor implements InterpolationPostProcessor {
+
+ private static final Set URL_EXPRESSIONS;
+
+ static {
+ Set expressions = new HashSet<>();
+ expressions.add("project.url");
+ expressions.add("project.scm.url");
+ expressions.add("project.scm.connection");
+ expressions.add("project.scm.developerConnection");
+ expressions.add("project.distributionManagement.site.url");
+ URL_EXPRESSIONS = expressions;
+ }
+
+ private final UrlNormalizer normalizer;
+
+ UrlNormalizingPostProcessor(UrlNormalizer normalizer) {
+ this.normalizer = normalizer;
+ }
+
+ @Override
+ public Object execute(String expression, Object value) {
+ if (value != null && URL_EXPRESSIONS.contains(expression)) {
+ return normalizer.normalize(value.toString());
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Wraps an arbitrary object with an {@link ObjectBasedValueSource} instance, then
+ * wraps that source with a {@link PrefixedValueSourceWrapper} instance, to which
+ * this class delegates all of its calls.
+ */
+ public static class PrefixedObjectValueSource extends AbstractDelegatingValueSource
+ implements QueryEnabledValueSource {
+
+ /**
+ * Wrap the specified root object, allowing the specified expression prefix.
+ * @param prefix the prefix.
+ * @param root the root of the graph.
+ */
+ public PrefixedObjectValueSource(String prefix, Object root) {
+ super(new PrefixedValueSourceWrapper(new ObjectBasedValueSource(root), prefix));
+ }
+
+ /**
+ * Wrap the specified root object, allowing the specified list of expression
+ * prefixes and setting whether the {@link PrefixedValueSourceWrapper} allows
+ * unprefixed expressions.
+ * @param possiblePrefixes The possible prefixes.
+ * @param root The root of the graph.
+ * @param allowUnprefixedExpressions if we allow undefined expressions or not.
+ */
+ public PrefixedObjectValueSource(
+ List possiblePrefixes, Object root, boolean allowUnprefixedExpressions) {
+ super(new PrefixedValueSourceWrapper(
+ new ObjectBasedValueSource(root), possiblePrefixes, allowUnprefixedExpressions));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getLastExpression() {
+ return ((QueryEnabledValueSource) getDelegate()).getLastExpression();
+ }
+ }
+
+ /**
+ * Wraps an object, providing reflective access to the object graph of which the
+ * supplied object is the root. Expressions like 'child.name' will translate into
+ * 'rootObject.getChild().getName()' for non-boolean properties, and
+ * 'rootObject.getChild().isName()' for boolean properties.
+ */
+ public static class ObjectBasedValueSource extends AbstractValueSource {
+
+ private final Object root;
+
+ /**
+ * Construct a new value source, using the supplied object as the root from
+ * which to start, and using expressions split at the dot ('.') to navigate
+ * the object graph beneath this root.
+ * @param root the root of the graph.
+ */
+ public ObjectBasedValueSource(Object root) {
+ super(true);
+ this.root = root;
+ }
+
+ /**
+ *
Split the expression into parts, tokenized on the dot ('.') character. Then,
+ * starting at the root object contained in this value source, apply each part
+ * to the object graph below this root, using either 'getXXX()' or 'isXXX()'
+ * accessor types to resolve the value for each successive expression part.
+ * Finally, return the result of the last expression part's resolution.
+ *
+ *
NOTE: The object-graph nagivation actually takes place via the
+ * {@link ReflectionValueExtractor} class.
+ */
+ public Object getValue(String expression) {
+ if (expression == null || expression.trim().isEmpty()) {
+ return null;
+ }
+
+ try {
+ return ReflectionValueExtractor.evaluate(expression, root, false);
+ } catch (Exception e) {
+ addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Wraps another value source and intercepts interpolated expressions, checking for problems.
+ *
+ */
+ static class ProblemDetectingValueSource implements ValueSource {
+
+ private final ValueSource valueSource;
+
+ private final String bannedPrefix;
+
+ private final String newPrefix;
+
+ private final ModelProblemCollector problems;
+
+ ProblemDetectingValueSource(
+ ValueSource valueSource, String bannedPrefix, String newPrefix, ModelProblemCollector problems) {
+ this.valueSource = valueSource;
+ this.bannedPrefix = bannedPrefix;
+ this.newPrefix = newPrefix;
+ this.problems = problems;
+ }
+
+ @Override
+ public Object getValue(String expression) {
+ Object value = valueSource.getValue(expression);
+
+ if (value != null && expression.startsWith(bannedPrefix)) {
+ String msg = "The expression ${" + expression + "} is deprecated.";
+ if (newPrefix != null && !newPrefix.isEmpty()) {
+ msg += " Please use ${" + newPrefix + expression.substring(bannedPrefix.length()) + "} instead.";
+ }
+ problems.add(BuilderProblem.Severity.WARNING, ModelProblem.Version.V20, msg);
+ }
+
+ return value;
+ }
+
+ @Override
+ public List getFeedback() {
+ return valueSource.getFeedback();
+ }
+
+ @Override
+ public void clearFeedback() {
+ valueSource.clearFeedback();
+ }
+ }
+
+ static class BuildTimestampValueSource extends AbstractValueSource {
+ private final Instant startTime;
+ private final Map properties;
+
+ BuildTimestampValueSource(Instant startTime, Map properties) {
+ super(false);
+ this.startTime = startTime;
+ this.properties = properties;
+ }
+
+ @Override
+ public Object getValue(String expression) {
+ if ("build.timestamp".equals(expression) || "maven.build.timestamp".equals(expression)) {
+ return new MavenBuildTimestamp(startTime, properties).formattedTimestamp();
+ }
+ return null;
+ }
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelNormalizer.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelNormalizer.java
new file mode 100644
index 000000000000..8a81133c1609
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelNormalizer.java
@@ -0,0 +1,146 @@
+/*
+ * 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.internal.impl.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Build;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.model.*;
+
+/**
+ * Handles normalization of a model.
+ *
+ */
+@Named
+@Singleton
+public class DefaultModelNormalizer implements ModelNormalizer {
+
+ private DuplicateMerger merger = new DuplicateMerger();
+
+ @Override
+ public Model mergeDuplicates(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model.Builder builder = Model.newBuilder(model);
+
+ Build build = model.getBuild();
+ if (build != null) {
+ List plugins = build.getPlugins();
+ Map normalized = new LinkedHashMap<>(plugins.size() * 2);
+
+ for (Plugin plugin : plugins) {
+ Object key = plugin.getKey();
+ Plugin first = normalized.get(key);
+ if (first != null) {
+ plugin = merger.mergePlugin(plugin, first);
+ }
+ normalized.put(key, plugin);
+ }
+
+ if (plugins.size() != normalized.size()) {
+ builder.build(
+ Build.newBuilder(build).plugins(normalized.values()).build());
+ }
+ }
+
+ /*
+ * NOTE: This is primarily to keep backward-compat with Maven 2.x which did not validate that dependencies are
+ * unique within a single POM. Upon multiple declarations, 2.x just kept the last one but retained the order of
+ * the first occurrence. So when we're in lenient/compat mode, we have to deal with such broken POMs and mimic
+ * the way 2.x works. When we're in strict mode, the removal of duplicates just saves other merging steps from
+ * aftereffects and bogus error messages.
+ */
+ List dependencies = model.getDependencies();
+ Map normalized = new LinkedHashMap<>(dependencies.size() * 2);
+
+ for (Dependency dependency : dependencies) {
+ normalized.put(dependency.getManagementKey(), dependency);
+ }
+
+ if (dependencies.size() != normalized.size()) {
+ builder.dependencies(normalized.values());
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * DuplicateMerger
+ */
+ protected static class DuplicateMerger extends MavenModelMerger {
+
+ public Plugin mergePlugin(Plugin target, Plugin source) {
+ return super.mergePlugin(target, source, false, Collections.emptyMap());
+ }
+ }
+
+ @Override
+ public Model injectDefaultValues(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model.Builder builder = Model.newBuilder(model);
+
+ builder.dependencies(injectList(model.getDependencies(), this::injectDependency));
+ Build build = model.getBuild();
+ if (build != null) {
+ Build newBuild = Build.newBuilder(build)
+ .plugins(injectList(build.getPlugins(), this::injectPlugin))
+ .build();
+ builder.build(newBuild != build ? newBuild : null);
+ }
+
+ return builder.build();
+ }
+
+ private Plugin injectPlugin(Plugin p) {
+ return Plugin.newBuilder(p)
+ .dependencies(injectList(p.getDependencies(), this::injectDependency))
+ .build();
+ }
+
+ private Dependency injectDependency(Dependency d) {
+ // we cannot set this directly in the MDO due to the interactions with dependency management
+ return (d.getScope() == null || d.getScope().isEmpty()) ? d.withScope("compile") : d;
+ }
+
+ /**
+ * Returns a list suited for the builders, i.e. null if not modified
+ */
+ private List injectList(List list, Function modifer) {
+ List newList = null;
+ for (int i = 0; i < list.size(); i++) {
+ T oldT = list.get(i);
+ T newT = modifer.apply(oldT);
+ if (newT != oldT) {
+ if (newList == null) {
+ newList = new ArrayList<>(list);
+ }
+ newList.set(i, newT);
+ }
+ }
+ return newList;
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelPathTranslator.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelPathTranslator.java
new file mode 100644
index 000000000000..4d8622eb9592
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelPathTranslator.java
@@ -0,0 +1,122 @@
+/*
+ * 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.internal.impl.model;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Build;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Reporting;
+import org.apache.maven.api.model.Resource;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.model.*;
+import org.apache.maven.internal.impl.model.DefaultTransformerContext.*;
+
+/**
+ * Resolves relative paths within a model against a specific base directory.
+ *
+ */
+@Named
+@Singleton
+public class DefaultModelPathTranslator implements ModelPathTranslator {
+
+ private final PathTranslator pathTranslator;
+
+ @Inject
+ public DefaultModelPathTranslator(PathTranslator pathTranslator) {
+ this.pathTranslator = pathTranslator;
+ }
+
+ @Override
+ public Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest request) {
+ if (model == null || basedir == null) {
+ return model;
+ }
+
+ Build build = model.getBuild();
+ Build newBuild = null;
+ if (build != null) {
+ newBuild = Build.newBuilder(build)
+ .directory(alignToBaseDirectory(build.getDirectory(), basedir))
+ .sourceDirectory(alignToBaseDirectory(build.getSourceDirectory(), basedir))
+ .testSourceDirectory(alignToBaseDirectory(build.getTestSourceDirectory(), basedir))
+ .scriptSourceDirectory(alignToBaseDirectory(build.getScriptSourceDirectory(), basedir))
+ .resources(map(build.getResources(), r -> alignToBaseDirectory(r, basedir)))
+ .testResources(map(build.getTestResources(), r -> alignToBaseDirectory(r, basedir)))
+ .filters(map(build.getFilters(), s -> alignToBaseDirectory(s, basedir)))
+ .outputDirectory(alignToBaseDirectory(build.getOutputDirectory(), basedir))
+ .testOutputDirectory(alignToBaseDirectory(build.getTestOutputDirectory(), basedir))
+ .build();
+ }
+
+ Reporting reporting = model.getReporting();
+ Reporting newReporting = null;
+ if (reporting != null) {
+ newReporting = Reporting.newBuilder(reporting)
+ .outputDirectory(alignToBaseDirectory(reporting.getOutputDirectory(), basedir))
+ .build();
+ }
+ if (newBuild != build || newReporting != reporting) {
+ model = Model.newBuilder(model)
+ .build(newBuild)
+ .reporting(newReporting)
+ .build();
+ }
+ return model;
+ }
+
+ private List map(List resources, Function mapper) {
+ List newResources = null;
+ if (resources != null) {
+ for (int i = 0; i < resources.size(); i++) {
+ T resource = resources.get(i);
+ T newResource = mapper.apply(resource);
+ if (newResource != null) {
+ if (newResources == null) {
+ newResources = new ArrayList<>(resources);
+ }
+ newResources.set(i, newResource);
+ }
+ }
+ }
+ return newResources;
+ }
+
+ private Resource alignToBaseDirectory(Resource resource, Path basedir) {
+ if (resource != null) {
+ String newDir = alignToBaseDirectory(resource.getDirectory(), basedir);
+ if (newDir != null) {
+ return resource.withDirectory(newDir);
+ }
+ }
+ return resource;
+ }
+
+ private String alignToBaseDirectory(String path, Path basedir) {
+ String newPath = pathTranslator.alignToBaseDirectory(path, basedir);
+ return Objects.equals(path, newPath) ? null : newPath;
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblem.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblem.java
new file mode 100644
index 000000000000..f30bffddf22a
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblem.java
@@ -0,0 +1,182 @@
+/*
+ * 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.internal.impl.model;
+
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.ModelProblem;
+
+/**
+ * Describes a problem that was encountered during model building. A problem can either be an exception that was thrown
+ * or a simple string message. In addition, a problem carries a hint about its source, e.g. the POM file that exhibits
+ * the problem.
+ *
+ */
+public class DefaultModelProblem implements ModelProblem {
+
+ private final String source;
+
+ private final int lineNumber;
+
+ private final int columnNumber;
+
+ private final String modelId;
+
+ private final String message;
+
+ private final Exception exception;
+
+ private final Severity severity;
+
+ private final Version version;
+
+ /**
+ * Creates a new problem with the specified message and exception.
+ *
+ * @param message The message describing the problem, may be {@code null}.
+ * @param severity The severity level of the problem, may be {@code null} to default to
+ * {@link Severity#ERROR}.
+ * @param source The source of the problem, may be {@code null}.
+ * @param lineNumber The one-based index of the line containing the error or {@code -1} if unknown.
+ * @param columnNumber The one-based index of the column containing the error or {@code -1} if unknown.
+ * @param exception The exception that caused this problem, may be {@code null}.
+ */
+ // mkleint: does this need to be public?
+ public DefaultModelProblem(
+ String message,
+ Severity severity,
+ Version version,
+ Model source,
+ int lineNumber,
+ int columnNumber,
+ Exception exception) {
+ this(
+ message,
+ severity,
+ version,
+ ModelProblemUtils.toPath(source),
+ lineNumber,
+ columnNumber,
+ ModelProblemUtils.toId(source),
+ exception);
+ }
+
+ /**
+ * Creates a new problem with the specified message and exception.
+ *
+ * @param message The message describing the problem, may be {@code null}.
+ * @param severity The severity level of the problem, may be {@code null} to default to
+ * {@link Severity#ERROR}.
+ * @param version The version since the problem is relevant
+ * @param source A hint about the source of the problem like a file path, may be {@code null}.
+ * @param lineNumber The one-based index of the line containing the problem or {@code -1} if unknown.
+ * @param columnNumber The one-based index of the column containing the problem or {@code -1} if unknown.
+ * @param modelId The identifier of the model that exhibits the problem, may be {@code null}.
+ * @param exception The exception that caused this problem, may be {@code null}.
+ */
+ // mkleint: does this need to be public?
+ @SuppressWarnings("checkstyle:parameternumber")
+ public DefaultModelProblem(
+ String message,
+ Severity severity,
+ Version version,
+ String source,
+ int lineNumber,
+ int columnNumber,
+ String modelId,
+ Exception exception) {
+ this.message = message;
+ this.severity = (severity != null) ? severity : Severity.ERROR;
+ this.source = (source != null) ? source : "";
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+ this.modelId = (modelId != null) ? modelId : "";
+ this.exception = exception;
+ this.version = version;
+ }
+
+ @Override
+ public String getSource() {
+ return source;
+ }
+
+ @Override
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public int getColumnNumber() {
+ return columnNumber;
+ }
+
+ public String getModelId() {
+ return modelId;
+ }
+
+ @Override
+ public Exception getException() {
+ return exception;
+ }
+
+ @Override
+ public String getLocation() {
+ return "";
+ }
+
+ @Override
+ public String getMessage() {
+ String msg = null;
+
+ if (message != null && !message.isEmpty()) {
+ msg = message;
+ } else if (exception != null) {
+ msg = exception.getMessage();
+ }
+
+ if (msg == null) {
+ msg = "";
+ }
+
+ return msg;
+ }
+
+ @Override
+ public Severity getSeverity() {
+ return severity;
+ }
+
+ public Version getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buffer = new StringBuilder(128);
+
+ buffer.append('[').append(getSeverity()).append("] ");
+ buffer.append(getMessage());
+ String location = ModelProblemUtils.formatLocation(this, null);
+ if (!location.isEmpty()) {
+ buffer.append(" @ ");
+ buffer.append(location);
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblemCollector.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblemCollector.java
new file mode 100644
index 000000000000..6a590b5967e9
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProblemCollector.java
@@ -0,0 +1,199 @@
+/*
+ * 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.internal.impl.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.api.model.InputLocation;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.BuilderProblem;
+import org.apache.maven.api.services.ModelBuilderException;
+import org.apache.maven.api.services.ModelBuilderResult;
+import org.apache.maven.api.services.ModelProblem;
+import org.apache.maven.api.services.model.ModelProblemCollector;
+import org.apache.maven.api.spi.ModelParserException;
+
+/**
+ * Collects problems that are encountered during model building. The primary purpose of this component is to account for
+ * the fact that the problem reporter has/should not have information about the calling context and hence cannot provide
+ * an expressive source hint for the model problem. Instead, the source hint is configured by the model builder before
+ * it delegates to other components that potentially encounter problems. Then, the problem reporter can focus on
+ * providing a simple error message, leaving the donkey work of creating a nice model problem to this component.
+ *
+ */
+class DefaultModelProblemCollector implements ModelProblemCollector {
+
+ private final ModelBuilderResult result;
+
+ private List problems;
+
+ private String source;
+
+ private Model sourceModel;
+
+ private Model rootModel;
+
+ private Set severities = EnumSet.noneOf(ModelProblem.Severity.class);
+
+ DefaultModelProblemCollector(ModelBuilderResult result) {
+ this.result = result;
+ this.problems = new ArrayList<>(result.getProblems());
+
+ for (ModelProblem problem : this.problems) {
+ severities.add(problem.getSeverity());
+ }
+ }
+
+ public boolean hasFatalErrors() {
+ return severities.contains(ModelProblem.Severity.FATAL);
+ }
+
+ public boolean hasErrors() {
+ return severities.contains(ModelProblem.Severity.ERROR) || severities.contains(ModelProblem.Severity.FATAL);
+ }
+
+ @Override
+ public List getProblems() {
+ return problems;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ this.sourceModel = null;
+ }
+
+ public void setSource(Model source) {
+ this.sourceModel = source;
+ this.source = null;
+
+ if (rootModel == null) {
+ rootModel = source;
+ }
+ }
+
+ private String getSource() {
+ if (source == null && sourceModel != null) {
+ source = ModelProblemUtils.toPath(sourceModel);
+ }
+ return source;
+ }
+
+ private String getModelId() {
+ return ModelProblemUtils.toId(sourceModel);
+ }
+
+ public void setRootModel(Model rootModel) {
+ this.rootModel = rootModel;
+ }
+
+ public Model getRootModel() {
+ return rootModel;
+ }
+
+ public String getRootModelId() {
+ return ModelProblemUtils.toId(rootModel);
+ }
+
+ @Override
+ public void add(ModelProblem problem) {
+ problems.add(problem);
+
+ severities.add(problem.getSeverity());
+ }
+
+ public void addAll(Collection problems) {
+ this.problems.addAll(problems);
+
+ for (ModelProblem problem : problems) {
+ severities.add(problem.getSeverity());
+ }
+ }
+
+ @Override
+ public void add(BuilderProblem.Severity severity, ModelProblem.Version version, String message) {
+ add(severity, version, message, null, null);
+ }
+
+ @Override
+ public void add(
+ BuilderProblem.Severity severity, ModelProblem.Version version, String message, InputLocation location) {
+ add(severity, version, message, location, null);
+ }
+
+ @Override
+ public void add(
+ BuilderProblem.Severity severity, ModelProblem.Version version, String message, Exception exception) {
+ add(severity, version, message, null, exception);
+ }
+
+ public void add(
+ BuilderProblem.Severity severity,
+ ModelProblem.Version version,
+ String message,
+ InputLocation location,
+ Exception exception) {
+ int line = -1;
+ int column = -1;
+ String source = null;
+ String modelId = null;
+
+ if (location != null) {
+ line = location.getLineNumber();
+ column = location.getColumnNumber();
+ if (location.getSource() != null) {
+ modelId = location.getSource().getModelId();
+ source = location.getSource().getLocation();
+ }
+ }
+
+ if (modelId == null) {
+ modelId = getModelId();
+ source = getSource();
+ }
+
+ if (line <= 0 && column <= 0 && exception instanceof ModelParserException e) {
+ line = e.getLineNumber();
+ column = e.getColumnNumber();
+ }
+
+ ModelProblem problem =
+ new DefaultModelProblem(message, severity, version, source, line, column, modelId, exception);
+
+ add(problem);
+ }
+
+ public ModelBuilderException newModelBuilderException() {
+ ModelBuilderResult result = this.result;
+ if (result.getModelIds().isEmpty()) {
+ DefaultModelBuilderResult tmp = new DefaultModelBuilderResult();
+ tmp.setEffectiveModel(result.getEffectiveModel());
+ tmp.setProblems(getProblems());
+ tmp.setActiveExternalProfiles(result.getActiveExternalProfiles());
+ String id = getRootModelId();
+ tmp.addModelId(id);
+ tmp.setRawModel(id, getRootModel());
+ result = tmp;
+ }
+ return new ModelBuilderException(result);
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProcessor.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProcessor.java
new file mode 100644
index 000000000000..879eb909579a
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelProcessor.java
@@ -0,0 +1,143 @@
+/*
+ * 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.internal.impl.model;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.services.model.*;
+import org.apache.maven.api.services.xml.ModelXmlFactory;
+import org.apache.maven.api.services.xml.XmlReaderRequest;
+import org.apache.maven.api.spi.ModelParser;
+import org.apache.maven.api.spi.ModelParserException;
+import org.apache.maven.internal.impl.model.DefaultTransformerContext.*;
+
+/**
+ *
+ * Note: uses @Typed to limit the types it is available for injection to just ModelProcessor.
+ *
+ * This is because the ModelProcessor interface extends ModelLocator and ModelReader. If we
+ * made this component available under all its interfaces then it could end up being injected
+ * into itself leading to a stack overflow.
+ *
+ * A side effect of using @Typed is that it translates to explicit bindings in the container.
+ * So instead of binding the component under a 'wildcard' key it is now bound with an explicit
+ * key. Since this is a default component this will be a plain binding of ModelProcessor to
+ * this implementation type, ie. no hint/name.
+ *
+ * This leads to a second side effect in that any @Inject request for just ModelProcessor in
+ * the same injector is immediately matched to this explicit binding, which means extensions
+ * cannot override this binding. This is because the lookup is always short-circuited in this
+ * specific situation (plain @Inject request, and plain explicit binding for the same type.)
+ *
+ * The simplest solution is to use a custom @Named here so it isn't bound under the plain key.
+ * This is only necessary for default components using @Typed that want to support overriding.
+ *
+ * As a non-default component this now gets a negative priority relative to other implementations
+ * of the same interface. Since we want to allow overriding this doesn't matter in this case.
+ * (if it did we could add @Priority of 0 to match the priority given to default components.)
+ */
+@Named
+@Singleton
+public class DefaultModelProcessor implements ModelProcessor {
+
+ private final ModelXmlFactory modelXmlFactory;
+ private final List modelParsers;
+
+ @Inject
+ public DefaultModelProcessor(ModelXmlFactory modelXmlFactory, List modelParsers) {
+ this.modelXmlFactory = modelXmlFactory;
+ this.modelParsers = modelParsers;
+ }
+
+ @Override
+ public Path locateExistingPom(Path projectDirectory) {
+ // Note that the ModelProcessor#locatePom never returns null
+ // while the ModelParser#locatePom needs to return an existing path!
+ Path pom = modelParsers.stream()
+ .map(m -> m.locate(projectDirectory)
+ .map(org.apache.maven.api.services.Source::getPath)
+ .orElse(null))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElseGet(() -> doLocateExistingPom(projectDirectory));
+ if (pom != null && !pom.equals(projectDirectory) && !pom.getParent().equals(projectDirectory)) {
+ throw new IllegalArgumentException("The POM found does not belong to the given directory: " + pom);
+ }
+ return pom;
+ }
+
+ @Override
+ public Model read(XmlReaderRequest request) throws IOException {
+ Objects.requireNonNull(request, "source cannot be null");
+ Path pomFile = request.getPath();
+ if (pomFile != null) {
+ Path projectDirectory = pomFile.getParent();
+ List exceptions = new ArrayList<>();
+ for (ModelParser parser : modelParsers) {
+ try {
+ Optional model =
+ parser.locateAndParse(projectDirectory, Map.of(ModelParser.STRICT, request.isStrict()));
+ if (model.isPresent()) {
+ return model.get().withPomFile(pomFile);
+ }
+ } catch (ModelParserException e) {
+ exceptions.add(e);
+ }
+ }
+ try {
+ return doRead(request);
+ } catch (IOException e) {
+ exceptions.forEach(e::addSuppressed);
+ throw e;
+ }
+ } else {
+ return doRead(request);
+ }
+ }
+
+ private Path doLocateExistingPom(Path project) {
+ if (project == null) {
+ project = Paths.get(System.getProperty("user.dir"));
+ }
+ if (Files.isDirectory(project)) {
+ Path pom = project.resolve("pom.xml");
+ return Files.isRegularFile(pom) ? pom : null;
+ } else if (Files.isRegularFile(project)) {
+ return project;
+ } else {
+ return null;
+ }
+ }
+
+ private Model doRead(XmlReaderRequest request) throws IOException {
+ return modelXmlFactory.read(request);
+ }
+}
diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
new file mode 100644
index 000000000000..7d33919c818f
--- /dev/null
+++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
@@ -0,0 +1,1742 @@
+/*
+ * 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.internal.impl.model;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.api.di.Inject;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.api.model.Activation;
+import org.apache.maven.api.model.ActivationFile;
+import org.apache.maven.api.model.Build;
+import org.apache.maven.api.model.BuildBase;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
+import org.apache.maven.api.model.DistributionManagement;
+import org.apache.maven.api.model.Exclusion;
+import org.apache.maven.api.model.InputLocation;
+import org.apache.maven.api.model.InputLocationTracker;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Parent;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.model.PluginExecution;
+import org.apache.maven.api.model.PluginManagement;
+import org.apache.maven.api.model.Profile;
+import org.apache.maven.api.model.ReportPlugin;
+import org.apache.maven.api.model.Reporting;
+import org.apache.maven.api.model.Repository;
+import org.apache.maven.api.model.Resource;
+import org.apache.maven.api.services.BuilderProblem.Severity;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.ModelProblem.Version;
+import org.apache.maven.api.services.model.*;
+import org.apache.maven.internal.impl.model.DefaultTransformerContext.*;
+import org.apache.maven.model.v4.MavenModelVersion;
+
+/**
+ */
+@Named
+@Singleton
+public class DefaultModelValidator implements ModelValidator {
+
+ public static final List VALID_MODEL_VERSIONS =
+ Collections.unmodifiableList(Arrays.asList("4.0.0", "4.1.0"));
+
+ private static final Pattern EXPRESSION_NAME_PATTERN = Pattern.compile("\\$\\{(.+?)}");
+ private static final Pattern EXPRESSION_PROJECT_NAME_PATTERN = Pattern.compile("\\$\\{(project.+?)}");
+
+ private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*";
+
+ private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS;
+
+ private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS;
+
+ private static final String EMPTY = "";
+
+ private final Set validCoordinateIds = new HashSet<>();
+
+ private final Set validProfileIds = new HashSet<>();
+
+ private final ModelVersionProcessor versionProcessor;
+
+ @Inject
+ public DefaultModelValidator(ModelVersionProcessor versionProcessor) {
+ this.versionProcessor = versionProcessor;
+ }
+
+ @Override
+ public void validateFileModel(Model m, ModelBuilderRequest request, ModelProblemCollector problems) {
+
+ Parent parent = m.getParent();
+ if (parent != null) {
+ validateStringNotEmpty(
+ "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(), parent);
+
+ validateStringNotEmpty(
+ "parent.artifactId", problems, Severity.FATAL, Version.BASE, parent.getArtifactId(), parent);
+
+ if (equals(parent.getGroupId(), m.getGroupId()) && equals(parent.getArtifactId(), m.getArtifactId())) {
+ addViolation(
+ problems,
+ Severity.FATAL,
+ Version.BASE,
+ "parent.artifactId",
+ null,
+ "must be changed"
+ + ", the parent element cannot have the same groupId:artifactId as the project.",
+ parent);
+ }
+
+ if (equals("LATEST", parent.getVersion()) || equals("RELEASE", parent.getVersion())) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.BASE,
+ "parent.version",
+ null,
+ "is either LATEST or RELEASE (both of them are being deprecated)",
+ parent);
+ }
+ }
+
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
+ Set modules = new HashSet<>();
+ for (int i = 0, n = m.getModules().size(); i < n; i++) {
+ String module = m.getModules().get(i);
+ if (!modules.add(module)) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ "modules.module[" + i + "]",
+ null,
+ "specifies duplicate child module " + module,
+ m.getLocation("modules"));
+ }
+ }
+
+ Severity errOn30 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0);
+
+ // The file pom may not contain the modelVersion yet, as it may be set later by the
+ // ModelVersionXMLFilter.
+ if (m.getModelVersion() != null && !m.getModelVersion().isEmpty()) {
+ validateModelVersion(problems, m.getModelVersion(), m, VALID_MODEL_VERSIONS);
+ }
+
+ validateStringNoExpression("groupId", problems, Severity.WARNING, Version.V20, m.getGroupId(), m);
+ if (parent == null) {
+ validateStringNotEmpty("groupId", problems, Severity.FATAL, Version.V20, m.getGroupId(), m);
+ }
+
+ validateStringNoExpression("artifactId", problems, Severity.WARNING, Version.V20, m.getArtifactId(), m);
+ validateStringNotEmpty("artifactId", problems, Severity.FATAL, Version.V20, m.getArtifactId(), m);
+
+ validateVersionNoExpression("version", problems, Severity.WARNING, Version.V20, m.getVersion(), m);
+ if (parent == null) {
+ validateStringNotEmpty("version", problems, Severity.FATAL, Version.V20, m.getVersion(), m);
+ }
+
+ validate20RawDependencies(problems, m.getDependencies(), "dependencies.dependency.", EMPTY, request);
+
+ validate20RawDependenciesSelfReferencing(
+ problems, m, m.getDependencies(), "dependencies.dependency", request);
+
+ if (m.getDependencyManagement() != null) {
+ validate20RawDependencies(
+ problems,
+ m.getDependencyManagement().getDependencies(),
+ "dependencyManagement.dependencies.dependency.",
+ EMPTY,
+ request);
+ }
+
+ validateRawRepositories(problems, m.getRepositories(), "repositories.repository.", EMPTY, request);
+
+ validateRawRepositories(
+ problems, m.getPluginRepositories(), "pluginRepositories.pluginRepository.", EMPTY, request);
+
+ Build build = m.getBuild();
+ if (build != null) {
+ validate20RawPlugins(problems, build.getPlugins(), "build.plugins.plugin.", EMPTY, request);
+
+ PluginManagement mgmt = build.getPluginManagement();
+ if (mgmt != null) {
+ validate20RawPlugins(
+ problems, mgmt.getPlugins(), "build.pluginManagement.plugins.plugin.", EMPTY, request);
+ }
+ }
+
+ Set profileIds = new HashSet<>();
+
+ for (Profile profile : m.getProfiles()) {
+ String prefix = "profiles.profile[" + profile.getId() + "].";
+
+ validateProfileId(prefix, "id", problems, Severity.ERROR, Version.V40, profile.getId(), null, m);
+
+ if (!profileIds.add(profile.getId())) {
+ addViolation(
+ problems,
+ errOn30,
+ Version.V20,
+ "profiles.profile.id",
+ null,
+ "must be unique but found duplicate profile with id " + profile.getId(),
+ profile);
+ }
+
+ validate30RawProfileActivation(problems, profile.getActivation(), prefix);
+
+ validate20RawDependencies(
+ problems, profile.getDependencies(), prefix, "dependencies.dependency.", request);
+
+ if (profile.getDependencyManagement() != null) {
+ validate20RawDependencies(
+ problems,
+ profile.getDependencyManagement().getDependencies(),
+ prefix,
+ "dependencyManagement.dependencies.dependency.",
+ request);
+ }
+
+ validateRawRepositories(
+ problems, profile.getRepositories(), prefix, "repositories.repository.", request);
+
+ validateRawRepositories(
+ problems,
+ profile.getPluginRepositories(),
+ prefix,
+ "pluginRepositories.pluginRepository.",
+ request);
+
+ BuildBase buildBase = profile.getBuild();
+ if (buildBase != null) {
+ validate20RawPlugins(problems, buildBase.getPlugins(), prefix, "plugins.plugin.", request);
+
+ PluginManagement mgmt = buildBase.getPluginManagement();
+ if (mgmt != null) {
+ validate20RawPlugins(
+ problems, mgmt.getPlugins(), prefix, "pluginManagement.plugins.plugin.", request);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void validateRawModel(Model m, ModelBuilderRequest request, ModelProblemCollector problems) {
+ // [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, VALID_MODEL_VERSIONS);
+
+ 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();
+
+ if (parent != null) {
+ validateStringNotEmpty(
+ "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(), parent);
+ }
+ }
+
+ private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
+ if (activation == null || activation.getFile() == null) {
+ return;
+ }
+
+ ActivationFile file = activation.getFile();
+
+ String path;
+ String location;
+
+ if (file.getExists() != null && !file.getExists().isEmpty()) {
+ path = file.getExists();
+ location = "exists";
+ } else if (file.getMissing() != null && !file.getMissing().isEmpty()) {
+ path = file.getMissing();
+ location = "missing";
+ } else {
+ return;
+ }
+
+ if (hasProjectExpression(path)) {
+ Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path);
+ while (matcher.find()) {
+ String propertyName = matcher.group(0);
+ if (!"${project.basedir}".equals(propertyName)) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.V30,
+ prefix + "activation.file." + location,
+ null,
+ "Failed to interpolate file location " + path + ": " + propertyName
+ + " expressions are not supported during profile activation.",
+ file.getLocation(location));
+ }
+ }
+ }
+ }
+
+ private void validate20RawPlugins(
+ ModelProblemCollector problems,
+ List plugins,
+ String prefix,
+ String prefix2,
+ ModelBuilderRequest request) {
+ Severity errOn31 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_1);
+
+ Map index = new HashMap<>();
+
+ for (Plugin plugin : plugins) {
+ if (plugin.getGroupId() == null
+ || (plugin.getGroupId() != null
+ && plugin.getGroupId().trim().isEmpty())) {
+ addViolation(
+ problems,
+ Severity.FATAL,
+ Version.V20,
+ prefix + prefix2 + "(groupId:artifactId)",
+ null,
+ "groupId of a plugin must be defined. ",
+ plugin);
+ }
+
+ if (plugin.getArtifactId() == null
+ || (plugin.getArtifactId() != null
+ && plugin.getArtifactId().trim().isEmpty())) {
+ addViolation(
+ problems,
+ Severity.FATAL,
+ Version.V20,
+ prefix + prefix2 + "(groupId:artifactId)",
+ null,
+ "artifactId of a plugin must be defined. ",
+ plugin);
+ }
+
+ // This will catch cases like or
+ if (plugin.getVersion() != null && plugin.getVersion().trim().isEmpty()) {
+ addViolation(
+ problems,
+ Severity.FATAL,
+ Version.V20,
+ prefix + prefix2 + "(groupId:artifactId)",
+ null,
+ "version of a plugin must be defined. ",
+ plugin);
+ }
+
+ String key = plugin.getKey();
+
+ Plugin existing = index.get(key);
+
+ if (existing != null) {
+ addViolation(
+ problems,
+ errOn31,
+ Version.V20,
+ prefix + prefix2 + "(groupId:artifactId)",
+ null,
+ "must be unique but found duplicate declaration of plugin " + key,
+ plugin);
+ } else {
+ index.put(key, plugin);
+ }
+
+ Set executionIds = new HashSet<>();
+
+ for (PluginExecution exec : plugin.getExecutions()) {
+ if (!executionIds.add(exec.getId())) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ prefix + prefix2 + "[" + plugin.getKey() + "].executions.execution.id",
+ null,
+ "must be unique but found duplicate execution with id " + exec.getId(),
+ exec);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void validateEffectiveModel(Model m, ModelBuilderRequest request, ModelProblemCollector problems) {
+ validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.BASE, m.getModelVersion(), m);
+
+ validateCoordinateId("groupId", problems, m.getGroupId(), m);
+
+ validateCoordinateId("artifactId", problems, m.getArtifactId(), m);
+
+ validateStringNotEmpty("packaging", problems, Severity.ERROR, Version.BASE, m.getPackaging(), m);
+
+ if (!m.getModules().isEmpty()) {
+ if (!"pom".equals(m.getPackaging())) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.BASE,
+ "packaging",
+ null,
+ "with value '" + m.getPackaging() + "' is invalid. Aggregator projects "
+ + "require 'pom' as packaging.",
+ m);
+ }
+
+ for (int i = 0, n = m.getModules().size(); i < n; i++) {
+ String module = m.getModules().get(i);
+
+ boolean isBlankModule = true;
+ if (module != null) {
+ for (int j = 0; j < module.length(); j++) {
+ if (!Character.isWhitespace(module.charAt(j))) {
+ isBlankModule = false;
+ }
+ }
+ }
+
+ if (isBlankModule) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.BASE,
+ "modules.module[" + i + "]",
+ null,
+ "has been specified without a path to the project directory.",
+ m.getLocation("modules"));
+ }
+ }
+ }
+
+ validateStringNotEmpty("version", problems, Severity.ERROR, Version.BASE, m.getVersion(), m);
+
+ Severity errOn30 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0);
+
+ validateEffectiveDependencies(problems, m, m.getDependencies(), false, request);
+
+ DependencyManagement mgmt = m.getDependencyManagement();
+ if (mgmt != null) {
+ validateEffectiveDependencies(problems, m, mgmt.getDependencies(), true, request);
+ }
+
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
+ Severity errOn31 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_1);
+
+ validateBannedCharacters(
+ EMPTY, "version", problems, errOn31, Version.V20, m.getVersion(), null, m, ILLEGAL_VERSION_CHARS);
+ validate20ProperSnapshotVersion("version", problems, errOn31, Version.V20, m.getVersion(), null, m);
+
+ Build build = m.getBuild();
+ if (build != null) {
+ for (Plugin p : build.getPlugins()) {
+ validateStringNotEmpty(
+ "build.plugins.plugin.artifactId",
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ p.getArtifactId(),
+ p);
+
+ validateStringNotEmpty(
+ "build.plugins.plugin.groupId", problems, Severity.ERROR, Version.V20, p.getGroupId(), p);
+
+ validate20PluginVersion(
+ "build.plugins.plugin.version", problems, p.getVersion(), p.getKey(), p, request);
+
+ validateBoolean(
+ "build.plugins.plugin.inherited",
+ EMPTY,
+ problems,
+ errOn30,
+ Version.V20,
+ p.getInherited(),
+ p.getKey(),
+ p);
+
+ validateBoolean(
+ "build.plugins.plugin.extensions",
+ EMPTY,
+ problems,
+ errOn30,
+ Version.V20,
+ p.getExtensions(),
+ p.getKey(),
+ p);
+
+ validate20EffectivePluginDependencies(problems, p, request);
+ }
+
+ validate20RawResources(problems, build.getResources(), "build.resources.resource.", request);
+
+ validate20RawResources(
+ problems, build.getTestResources(), "build.testResources.testResource.", request);
+ }
+
+ Reporting reporting = m.getReporting();
+ if (reporting != null) {
+ for (ReportPlugin p : reporting.getPlugins()) {
+ validateStringNotEmpty(
+ "reporting.plugins.plugin.artifactId",
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ p.getArtifactId(),
+ p);
+
+ validateStringNotEmpty(
+ "reporting.plugins.plugin.groupId",
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ p.getGroupId(),
+ p);
+ }
+ }
+
+ for (Repository repository : m.getRepositories()) {
+ validate20EffectiveRepository(problems, repository, "repositories.repository.", request);
+ }
+
+ for (Repository repository : m.getPluginRepositories()) {
+ validate20EffectiveRepository(problems, repository, "pluginRepositories.pluginRepository.", request);
+ }
+
+ DistributionManagement distMgmt = m.getDistributionManagement();
+ if (distMgmt != null) {
+ if (distMgmt.getStatus() != null) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ "distributionManagement.status",
+ null,
+ "must not be specified.",
+ distMgmt);
+ }
+
+ validate20EffectiveRepository(
+ problems, distMgmt.getRepository(), "distributionManagement.repository.", request);
+ validate20EffectiveRepository(
+ problems,
+ distMgmt.getSnapshotRepository(),
+ "distributionManagement.snapshotRepository.",
+ request);
+ }
+ }
+ }
+
+ private void validate20RawDependencies(
+ ModelProblemCollector problems,
+ List dependencies,
+ String prefix,
+ String prefix2,
+ ModelBuilderRequest request) {
+ Severity errOn30 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0);
+ Severity errOn31 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_1);
+
+ Map index = new HashMap<>();
+
+ for (Dependency dependency : dependencies) {
+ String key = dependency.getManagementKey();
+
+ if ("import".equals(dependency.getScope())) {
+ if (!"pom".equals(dependency.getType())) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ prefix + prefix2 + "type",
+ key,
+ "must be 'pom' to import the managed dependencies.",
+ dependency);
+ } else if (dependency.getClassifier() != null
+ && !dependency.getClassifier().isEmpty()) {
+ addViolation(
+ problems,
+ errOn30,
+ Version.V20,
+ prefix + prefix2 + "classifier",
+ key,
+ "must be empty, imported POM cannot have a classifier.",
+ dependency);
+ }
+ } else if ("system".equals(dependency.getScope())) {
+
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_1) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.V31,
+ prefix + prefix2 + "scope",
+ key,
+ "declares usage of deprecated 'system' scope ",
+ dependency);
+ }
+
+ String sysPath = dependency.getSystemPath();
+ if (sysPath != null && !sysPath.isEmpty()) {
+ if (!hasExpression(sysPath)) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ prefix + prefix2 + "systemPath",
+ key,
+ "should use a variable instead of a hard-coded path " + sysPath,
+ dependency);
+ } else if (sysPath.contains("${basedir}") || sysPath.contains("${project.basedir}")) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ prefix + prefix2 + "systemPath",
+ key,
+ "should not point at files within the project directory, " + sysPath
+ + " will be unresolvable by dependent projects",
+ dependency);
+ }
+ }
+ }
+
+ if (equals("LATEST", dependency.getVersion()) || equals("RELEASE", dependency.getVersion())) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.BASE,
+ prefix + prefix2 + "version",
+ key,
+ "is either LATEST or RELEASE (both of them are being deprecated)",
+ dependency);
+ }
+
+ Dependency existing = index.get(key);
+
+ if (existing != null) {
+ String msg;
+ if (equals(existing.getVersion(), dependency.getVersion())) {
+ msg = "duplicate declaration of version " + Objects.toString(dependency.getVersion(), "(?)");
+ } else {
+ msg = "version " + Objects.toString(existing.getVersion(), "(?)") + " vs "
+ + Objects.toString(dependency.getVersion(), "(?)");
+ }
+
+ addViolation(
+ problems,
+ errOn31,
+ Version.V20,
+ prefix + prefix2 + "(groupId:artifactId:type:classifier)",
+ null,
+ "must be unique: " + key + " -> " + msg,
+ dependency);
+ } else {
+ index.put(key, dependency);
+ }
+ }
+ }
+
+ private void validate20RawDependenciesSelfReferencing(
+ ModelProblemCollector problems,
+ org.apache.maven.api.model.Model m,
+ List dependencies,
+ String prefix,
+ ModelBuilderRequest request) {
+ // We only check for groupId/artifactId/version/classifier cause if there is another
+ // module with the same groupId/artifactId/version/classifier this will fail the build
+ // earlier like "Project '...' is duplicated in the reactor.
+ // So it is sufficient to check only groupId/artifactId/version/classifier and not the
+ // packaging type.
+ for (Dependency dependency : dependencies) {
+ String key = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion()
+ + (dependency.getClassifier() != null ? ":" + dependency.getClassifier() : EMPTY);
+ String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
+ if (key.equals(mKey)) {
+ // This means a module which is build has a dependency which has the same
+ // groupId, artifactId, version and classifier coordinates. This is in consequence
+ // a self reference or in other words a circular reference which can not being resolved.
+ addViolation(
+ problems,
+ Severity.FATAL,
+ Version.V31,
+ prefix + "[" + key + "]",
+ key,
+ "is referencing itself.",
+ dependency);
+ }
+ }
+ }
+
+ private void validateEffectiveDependencies(
+ ModelProblemCollector problems,
+ org.apache.maven.api.model.Model m,
+ List dependencies,
+ boolean management,
+ ModelBuilderRequest request) {
+ Severity errOn30 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0);
+
+ String prefix = management ? "dependencyManagement.dependencies.dependency." : "dependencies.dependency.";
+
+ for (Dependency d : dependencies) {
+ validateEffectiveDependency(problems, d, management, prefix, request);
+
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
+ validateBoolean(
+ prefix, "optional", problems, errOn30, Version.V20, d.getOptional(), d.getManagementKey(), d);
+
+ if (!management) {
+ validateVersion(
+ prefix, "version", problems, errOn30, Version.V20, d.getVersion(), d.getManagementKey(), d);
+
+ /*
+ * TODO Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In
+ * order to don't break backward-compat with those, only warn but don't error out.
+ */
+ validateEnum(
+ prefix,
+ "scope",
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ d.getScope(),
+ d.getManagementKey(),
+ d,
+ "provided",
+ "compile",
+ "runtime",
+ "test",
+ "system");
+
+ validateEffectiveModelAgainstDependency(prefix, problems, m, d, request);
+ } else {
+ validateEnum(
+ prefix,
+ "scope",
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ d.getScope(),
+ d.getManagementKey(),
+ d,
+ "provided",
+ "compile",
+ "runtime",
+ "test",
+ "system",
+ "import");
+ }
+ }
+ }
+ }
+
+ private void validateEffectiveModelAgainstDependency(
+ String prefix,
+ ModelProblemCollector problems,
+ org.apache.maven.api.model.Model m,
+ Dependency d,
+ ModelBuilderRequest request) {
+ String key = d.getGroupId() + ":" + d.getArtifactId() + ":" + d.getVersion()
+ + (d.getClassifier() != null ? ":" + d.getClassifier() : EMPTY);
+ String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
+ if (key.equals(mKey)) {
+ // This means a module which is build has a dependency which has the same
+ // groupId, artifactId, version and classifier coordinates. This is in consequence
+ // a self reference or in other words a circular reference which can not being resolved.
+ addViolation(
+ problems, Severity.FATAL, Version.V31, prefix + "[" + key + "]", key, "is referencing itself.", d);
+ }
+ }
+
+ private void validate20EffectivePluginDependencies(
+ ModelProblemCollector problems, Plugin plugin, ModelBuilderRequest request) {
+ List dependencies = plugin.getDependencies();
+
+ if (!dependencies.isEmpty()) {
+ String prefix = "build.plugins.plugin[" + plugin.getKey() + "].dependencies.dependency.";
+
+ Severity errOn30 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0);
+
+ for (Dependency d : dependencies) {
+ validateEffectiveDependency(problems, d, false, prefix, request);
+
+ validateVersion(
+ prefix, "version", problems, errOn30, Version.BASE, d.getVersion(), d.getManagementKey(), d);
+
+ validateEnum(
+ prefix,
+ "scope",
+ problems,
+ errOn30,
+ Version.BASE,
+ d.getScope(),
+ d.getManagementKey(),
+ d,
+ "compile",
+ "runtime",
+ "system");
+ }
+ }
+ }
+
+ private void validateEffectiveDependency(
+ ModelProblemCollector problems,
+ Dependency d,
+ boolean management,
+ String prefix,
+ ModelBuilderRequest request) {
+ validateCoordinateId(
+ prefix,
+ "artifactId",
+ problems,
+ Severity.ERROR,
+ Version.BASE,
+ d.getArtifactId(),
+ d.getManagementKey(),
+ d);
+
+ validateCoordinateId(
+ prefix, "groupId", problems, Severity.ERROR, Version.BASE, d.getGroupId(), d.getManagementKey(), d);
+
+ if (!management) {
+ validateStringNotEmpty(
+ prefix, "type", problems, Severity.ERROR, Version.BASE, d.getType(), d.getManagementKey(), d);
+
+ validateDependencyVersion(problems, d, prefix);
+ }
+
+ if ("system".equals(d.getScope())) {
+ String systemPath = d.getSystemPath();
+
+ if (systemPath == null || systemPath.isEmpty()) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.BASE,
+ prefix + "systemPath",
+ d.getManagementKey(),
+ "is missing.",
+ d);
+ } else {
+ File sysFile = new File(systemPath);
+ if (!sysFile.isAbsolute()) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.BASE,
+ prefix + "systemPath",
+ d.getManagementKey(),
+ "must specify an absolute path but is " + systemPath,
+ d);
+ } else if (!sysFile.isFile()) {
+ String msg = "refers to a non-existing file " + sysFile.getAbsolutePath();
+ systemPath = systemPath.replace('/', File.separatorChar).replace('\\', File.separatorChar);
+ String jdkHome = request.getSystemProperties().get("java.home") + File.separator + "..";
+ if (systemPath.startsWith(jdkHome)) {
+ msg += ". Please verify that you run Maven using a JDK and not just a JRE.";
+ }
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.BASE,
+ prefix + "systemPath",
+ d.getManagementKey(),
+ msg,
+ d);
+ }
+ }
+ } else if (d.getSystemPath() != null && !d.getSystemPath().isEmpty()) {
+ addViolation(
+ problems,
+ Severity.ERROR,
+ Version.BASE,
+ prefix + "systemPath",
+ d.getManagementKey(),
+ "must be omitted. This field may only be specified for a dependency with system scope.",
+ d);
+ }
+
+ if (request.getValidationLevel() >= ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_2_0) {
+ for (Exclusion exclusion : d.getExclusions()) {
+ if (request.getValidationLevel() < ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0) {
+ validateCoordinateId(
+ prefix,
+ "exclusions.exclusion.groupId",
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ exclusion.getGroupId(),
+ d.getManagementKey(),
+ exclusion);
+
+ validateCoordinateId(
+ prefix,
+ "exclusions.exclusion.artifactId",
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ exclusion.getArtifactId(),
+ d.getManagementKey(),
+ exclusion);
+ } else {
+ validateCoordinateIdWithWildcards(
+ prefix,
+ "exclusions.exclusion.groupId",
+ problems,
+ Severity.WARNING,
+ Version.V30,
+ exclusion.getGroupId(),
+ d.getManagementKey(),
+ exclusion);
+
+ validateCoordinateIdWithWildcards(
+ prefix,
+ "exclusions.exclusion.artifactId",
+ problems,
+ Severity.WARNING,
+ Version.V30,
+ exclusion.getArtifactId(),
+ d.getManagementKey(),
+ exclusion);
+ }
+ }
+ }
+ }
+
+ /**
+ * @since 3.2.4
+ */
+ protected void validateDependencyVersion(ModelProblemCollector problems, Dependency d, String prefix) {
+ validateStringNotEmpty(
+ prefix, "version", problems, Severity.ERROR, Version.BASE, d.getVersion(), d.getManagementKey(), d);
+ }
+
+ private void validateRawRepositories(
+ ModelProblemCollector problems,
+ List repositories,
+ String prefix,
+ String prefix2,
+ ModelBuilderRequest request) {
+ Map index = new HashMap<>();
+
+ for (Repository repository : repositories) {
+ validateStringNotEmpty(
+ prefix, prefix2, "id", problems, Severity.ERROR, Version.V20, repository.getId(), null, repository);
+
+ if (validateStringNotEmpty(
+ prefix,
+ prefix2,
+ "[" + repository.getId() + "].url",
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ repository.getUrl(),
+ null,
+ repository)) {
+ // only allow ${basedir} and ${project.basedir}
+ Matcher m = EXPRESSION_NAME_PATTERN.matcher(repository.getUrl());
+ while (m.find()) {
+ if (!("basedir".equals(m.group(1)) || "project.basedir".equals(m.group(1)))) {
+ validateStringNoExpression(
+ prefix + prefix2 + "[" + repository.getId() + "].url",
+ problems,
+ Severity.ERROR,
+ Version.V40,
+ repository.getUrl(),
+ repository);
+ break;
+ }
+ }
+ }
+
+ String key = repository.getId();
+
+ Repository existing = index.get(key);
+
+ if (existing != null) {
+ Severity errOn30 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0);
+
+ addViolation(
+ problems,
+ errOn30,
+ Version.V20,
+ prefix + prefix2 + "id",
+ null,
+ "must be unique: " + repository.getId() + " -> " + existing.getUrl() + " vs "
+ + repository.getUrl(),
+ repository);
+ } else {
+ index.put(key, repository);
+ }
+ }
+ }
+
+ private void validate20EffectiveRepository(
+ ModelProblemCollector problems, Repository repository, String prefix, ModelBuilderRequest request) {
+ if (repository != null) {
+ Severity errOn31 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_1);
+
+ validateBannedCharacters(
+ prefix,
+ "id",
+ problems,
+ errOn31,
+ Version.V20,
+ repository.getId(),
+ null,
+ repository,
+ ILLEGAL_REPO_ID_CHARS);
+
+ if ("local".equals(repository.getId())) {
+ addViolation(
+ problems,
+ errOn31,
+ Version.V20,
+ prefix + "id",
+ null,
+ "must not be 'local'" + ", this identifier is reserved for the local repository"
+ + ", using it for other repositories will corrupt your repository metadata.",
+ repository);
+ }
+
+ if ("legacy".equals(repository.getLayout())) {
+ addViolation(
+ problems,
+ Severity.WARNING,
+ Version.V20,
+ prefix + "layout",
+ repository.getId(),
+ "uses the unsupported value 'legacy', artifact resolution might fail.",
+ repository);
+ }
+ }
+ }
+
+ private void validate20RawResources(
+ ModelProblemCollector problems, List resources, String prefix, ModelBuilderRequest request) {
+ Severity errOn30 = getSeverity(request, ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_3_0);
+
+ for (Resource resource : resources) {
+ validateStringNotEmpty(
+ prefix,
+ "directory",
+ problems,
+ Severity.ERROR,
+ Version.V20,
+ resource.getDirectory(),
+ null,
+ resource);
+
+ validateBoolean(
+ prefix,
+ "filtering",
+ problems,
+ errOn30,
+ Version.V20,
+ resource.getFiltering(),
+ resource.getDirectory(),
+ resource);
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Field validation
+ // ----------------------------------------------------------------------
+
+ private boolean validateCoordinateId(
+ String fieldName, ModelProblemCollector problems, String id, InputLocationTracker tracker) {
+ return validateCoordinateId(EMPTY, fieldName, problems, Severity.ERROR, Version.BASE, id, null, tracker);
+ }
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ private boolean validateCoordinateId(
+ String prefix,
+ String fieldName,
+ ModelProblemCollector problems,
+ Severity severity,
+ Version version,
+ String id,
+ String sourceHint,
+ InputLocationTracker tracker) {
+ if (validCoordinateIds.contains(id)) {
+ return true;
+ }
+ if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
+ return false;
+ } else {
+ if (!isValidCoordinateId(id)) {
+ addViolation(
+ problems,
+ severity,
+ version,
+ prefix + fieldName,
+ sourceHint,
+ "with value '" + id + "' does not match a valid coordinate id pattern.",
+ tracker);
+ return false;
+ }
+ validCoordinateIds.add(id);
+ return true;
+ }
+ }
+
+ private boolean isValidCoordinateId(String id) {
+ for (int i = 0; i < id.length(); i++) {
+ char c = id.charAt(i);
+ if (!isValidCoordinateIdCharacter(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isValidCoordinateIdCharacter(char c) {
+ return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.';
+ }
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ private boolean validateProfileId(
+ String prefix,
+ String fieldName,
+ ModelProblemCollector problems,
+ Severity severity,
+ Version version,
+ String id,
+ String sourceHint,
+ InputLocationTracker tracker) {
+ if (validProfileIds.contains(id)) {
+ return true;
+ }
+ if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
+ return false;
+ } else {
+ if (!isValidProfileId(id)) {
+ addViolation(
+ problems,
+ severity,
+ version,
+ prefix + fieldName,
+ sourceHint,
+ "with value '" + id + "' does not match a valid profile id pattern.",
+ tracker);
+ return false;
+ }
+ validProfileIds.add(id);
+ return true;
+ }
+ }
+
+ private boolean isValidProfileId(String id) {
+ switch (id.charAt(0)) { // avoid first character that has special CLI meaning in "mvn -P xxx"
+ case '+': // activate
+ case '-': // deactivate
+ case '!': // deactivate
+ case '?': // optional
+ return false;
+ default:
+ }
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ private boolean validateCoordinateIdWithWildcards(
+ String prefix,
+ String fieldName,
+ ModelProblemCollector problems,
+ Severity severity,
+ Version version,
+ String id,
+ String sourceHint,
+ InputLocationTracker tracker) {
+ if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
+ return false;
+ } else {
+ if (!isValidCoordinateIdWithWildCards(id)) {
+ addViolation(
+ problems,
+ severity,
+ version,
+ prefix + fieldName,
+ sourceHint,
+ "with value '" + id + "' does not match a valid coordinate id pattern.",
+ tracker);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private boolean isValidCoordinateIdWithWildCards(String id) {
+ for (int i = 0; i < id.length(); i++) {
+ char c = id.charAt(i);
+ if (!isValidCoordinateIdWithWildCardCharacter(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isValidCoordinateIdWithWildCardCharacter(char c) {
+ return isValidCoordinateIdCharacter(c) || c == '?' || c == '*';
+ }
+
+ private boolean validateStringNoExpression(
+ String fieldName,
+ ModelProblemCollector problems,
+ Severity severity,
+ Version version,
+ String string,
+ InputLocationTracker tracker) {
+ if (!hasExpression(string)) {
+ return true;
+ }
+
+ addViolation(
+ problems,
+ severity,
+ version,
+ fieldName,
+ null,
+ "contains an expression but should be a constant.",
+ tracker);
+
+ return false;
+ }
+
+ private boolean validateVersionNoExpression(
+ String fieldName,
+ ModelProblemCollector problems,
+ Severity severity,
+ Version version,
+ String string,
+ InputLocationTracker tracker) {
+ if (!hasExpression(string)) {
+ return true;
+ }
+
+ Matcher m = EXPRESSION_NAME_PATTERN.matcher(string.trim());
+ while (m.find()) {
+ String property = m.group(1);
+ if (!versionProcessor.isValidProperty(property)) {
+ addViolation(
+ problems,
+ severity,
+ version,
+ fieldName,
+ null,
+ "contains an expression but should be a constant.",
+ tracker);
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean hasExpression(String value) {
+ return value != null && value.contains("${");
+ }
+
+ private boolean hasProjectExpression(String value) {
+ return value != null && value.contains("${project.");
+ }
+
+ private boolean validateStringNotEmpty(
+ String fieldName,
+ ModelProblemCollector problems,
+ Severity severity,
+ Version version,
+ String string,
+ InputLocationTracker tracker) {
+ return validateStringNotEmpty(EMPTY, fieldName, problems, severity, version, string, null, tracker);
+ }
+
+ /**
+ * Asserts:
+ *
+ *