You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2020/12/22 15:30:52 UTC

[maven] branch MNG-6957_squashed updated (37afe81 -> 9f88494)

This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a change to branch MNG-6957_squashed
in repository https://gitbox.apache.org/repos/asf/maven.git.


 discard 37afe81  [MNG-6957] Versionless reactor dependencies/parent should work even if modules are aggregated in reverse order
     add 2609cdc  [MNG-7045] Drop CDI API from Maven
     new 9f88494  [MNG-6957] Versionless reactor dependencies/parent should work even if modules are aggregated in reverse order

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (37afe81)
            \
             N -- N -- N   refs/heads/MNG-6957_squashed (9f88494)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 maven-core/src/main/resources/META-INF/maven/extension.xml | 4 +++-
 pom.xml                                                    | 6 ++++++
 2 files changed, 9 insertions(+), 1 deletion(-)


[maven] 01/01: [MNG-6957] Versionless reactor dependencies/parent should work even if modules are aggregated in reverse order

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch MNG-6957_squashed
in repository https://gitbox.apache.org/repos/asf/maven.git

commit 9f88494b6064ad45ea2e2e1e3478afc7af0bc065
Author: rfscholte <rf...@apache.org>
AuthorDate: Mon Dec 21 22:23:43 2020 +0100

    [MNG-6957] Versionless reactor dependencies/parent should work even if modules are aggregated in reverse order
    
    This closes #391
---
 .../aether/ConsumerModelSourceTransformer.java     |   4 +-
 .../maven/project/DefaultProjectBuilder.java       | 122 ++--
 .../apache/maven/project/ReactorModelCache.java    |   9 +-
 .../DefaultConsumerPomXMLFilterFactory.java        |  29 +-
 .../building/BuildModelSourceTransformer.java      |   2 +-
 .../building/DefaultBuildPomXMLFilterFactory.java  |  45 +-
 .../maven/model/building/DefaultModelBuilder.java  | 640 ++++++++++++++-------
 .../building/DefaultModelBuildingRequest.java      |  21 +-
 .../model/building/DefaultModelBuildingResult.java |  30 +
 .../model/building/DefaultTransformerContext.java  |  95 +++
 .../model/building/FilterModelBuildingRequest.java |  12 +-
 .../apache/maven/model/building/ModelBuilder.java  |   2 +
 .../maven/model/building/ModelBuildingRequest.java |  32 +-
 .../maven/model/building/ModelBuildingResult.java  |   7 +
 .../apache/maven/model/building/ModelCacheTag.java |  10 +-
 .../org/apache/maven/model/building/ModelData.java | 113 +---
 .../model/building/ModelSourceTransformer.java     |  14 +-
 .../maven/model/building/TransformerContext.java   |   6 +-
 ...sformer.java => TransformerContextBuilder.java} |  27 +-
 .../apache/maven/model/io/DefaultModelReader.java  |  14 +-
 .../org/apache/maven/model/io/ModelReader.java     |   6 +
 maven-model-builder/src/site/apt/index.apt         |  27 +-
 .../validation/DefaultModelValidatorTest.java      |   4 -
 .../xml/sax/filter/BuildPomXMLFilterFactory.java   |  45 +-
 .../maven/xml/sax/filter/CiFriendlyXMLFilter.java  |  31 +-
 .../sax/filter/ConsumerPomXMLFilterFactory.java    |  33 +-
 .../maven/xml/sax/filter/ParentXMLFilter.java      |  71 ++-
 .../xml/sax/filter/CiFriendlyXMLFilterTest.java    |   2 +-
 .../xml/sax/filter/ConsumerPomXMLFilterTest.java   |  17 +-
 .../maven/xml/sax/filter/ParentXMLFilterTest.java  |  37 +-
 30 files changed, 907 insertions(+), 600 deletions(-)

diff --git a/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java b/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java
index d81571d..4d5d263 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java
@@ -46,13 +46,13 @@ import org.xml.sax.ext.LexicalHandler;
 class ConsumerModelSourceTransformer extends AbstractModelSourceTransformer
 {
     @Override
-    protected AbstractSAXFilter getSAXFilter( Path pomFile, 
+    protected AbstractSAXFilter getSAXFilter( Path pomFile,
                                               TransformerContext context,
                                               Consumer<LexicalHandler> lexicalHandlerConsumer )
         throws TransformerConfigurationException, SAXException, ParserConfigurationException
     {
         return new DefaultConsumerPomXMLFilterFactory( new DefaultBuildPomXMLFilterFactory( context,
-                                                                        lexicalHandlerConsumer ) ).get( pomFile );
+                                                                        lexicalHandlerConsumer, true ) ).get( pomFile );
     }
     
     /**
diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index 624f6ad..308097d 100644
--- a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -21,7 +21,6 @@ package org.apache.maven.project;
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.file.Path;
 import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,6 +32,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -56,6 +56,7 @@ import org.apache.maven.model.Plugin;
 import org.apache.maven.model.Profile;
 import org.apache.maven.model.ReportPlugin;
 import org.apache.maven.model.building.ArtifactModelSource;
+import org.apache.maven.model.building.TransformerContextBuilder;
 import org.apache.maven.model.building.DefaultModelBuildingRequest;
 import org.apache.maven.model.building.DefaultModelProblem;
 import org.apache.maven.model.building.FileModelSource;
@@ -129,7 +130,7 @@ public class DefaultProjectBuilder
         throws ProjectBuildingException
     {
         return build( pomFile, new FileModelSource( pomFile ),
-                new InternalConfig( request, null, useGlobalModelCache() ? getModelCache() : null ) );
+                new InternalConfig( request, null, useGlobalModelCache() ? getModelCache() : null, null ) );
     }
 
     private boolean useGlobalModelCache()
@@ -142,7 +143,7 @@ public class DefaultProjectBuilder
         throws ProjectBuildingException
     {
         return build( null, modelSource,
-                 new InternalConfig( request, null, useGlobalModelCache() ? getModelCache() : null ) );
+                 new InternalConfig( request, null, useGlobalModelCache() ? getModelCache() : null, null ) );
     }
 
     private ProjectBuildingResult build( File pomFile, ModelSource modelSource, InternalConfig config )
@@ -263,14 +264,7 @@ public class DefaultProjectBuilder
 
     private List<String> getProfileIds( List<Profile> profiles )
     {
-        List<String> ids = new ArrayList<>( profiles.size() );
-
-        for ( Profile profile : profiles )
-        {
-            ids.add( profile.getId() );
-        }
-
-        return ids;
+        return profiles.stream().map( Profile::getId ).collect( Collectors.toList() );
     }
 
     private ModelBuildingRequest getModelBuildingRequest( InternalConfig config )
@@ -295,7 +289,7 @@ public class DefaultProjectBuilder
         request.setBuildStartTime( configuration.getBuildStartTime() );
         request.setModelResolver( resolver );
         request.setModelCache( config.modelCache );
-        request.setTransformerContext( (TransformerContext) config.session.getData().get( TransformerContext.KEY ) );
+        request.setTransformerContextBuilder( config.transformerContextBuilder );
 
         return request;
     }
@@ -314,7 +308,8 @@ public class DefaultProjectBuilder
         org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact( artifact );
         pomArtifact = ArtifactDescriptorUtils.toPomArtifact( pomArtifact );
 
-        InternalConfig config = new InternalConfig( request, null, useGlobalModelCache() ? getModelCache() : null );
+        InternalConfig config =
+            new InternalConfig( request, null, useGlobalModelCache() ? getModelCache() : null, null );
 
         boolean localProject;
 
@@ -385,37 +380,14 @@ public class DefaultProjectBuilder
 
         ReactorModelPool.Builder poolBuilder = new ReactorModelPool.Builder();
         final ReactorModelPool modelPool = poolBuilder.build();
-        
-        if ( Features.buildConsumer().isActive() )
-        {
-            final TransformerContext context = new TransformerContext()
-            {
-                @Override
-                public String getUserProperty( String key )
-                {
-                    return request.getUserProperties().getProperty( key );
-                }
-    
-                @Override
-                public Model getRawModel( Path p )
-                {
-                    return modelPool.get( p );
-                }
-    
-                @Override
-                public Model getRawModel( String groupId, String artifactId )
-                {
-                    return modelPool.get( groupId, artifactId, null );
-                }
-            };
-            request.getRepositorySession().getData().set( TransformerContext.KEY, context );
-        }
 
-        InternalConfig config = new InternalConfig( request, modelPool,
-                useGlobalModelCache() ? getModelCache() : new ReactorModelCache() );
+        InternalConfig config =
+            new InternalConfig( request, modelPool, useGlobalModelCache() ? getModelCache() : new ReactorModelCache(),
+                        modelBuilder.newTransformerContextBuilder() );
 
-        Map<String, MavenProject> projectIndex = new HashMap<>( 256 );
+        Map<File, MavenProject> projectIndex = new HashMap<>( 256 );
 
+        // phase 1: get file Models from the reactor.
         boolean noErrors =
             build( results, interimResults, projectIndex, pomFiles, new LinkedHashSet<>(), true, recursive,
                    config, poolBuilder );
@@ -424,6 +396,7 @@ public class DefaultProjectBuilder
 
         try
         {
+            // Phase 2: get effective models from the reactor
             noErrors =
                 build( results, new ArrayList<>(), projectIndex, interimResults, request,
                         new HashMap<>(), config.session ) && noErrors;
@@ -433,6 +406,12 @@ public class DefaultProjectBuilder
             Thread.currentThread().setContextClassLoader( oldContextClassLoader );
         }
 
+        if ( Features.buildConsumer().isActive() )
+        {
+            request.getRepositorySession().getData().set( TransformerContext.KEY,
+                                                          config.transformerContextBuilder.build() );
+        }
+
         if ( !noErrors )
         {
             throw new ProjectBuildingException( results );
@@ -443,7 +422,7 @@ public class DefaultProjectBuilder
 
     @SuppressWarnings( "checkstyle:parameternumber" )
     private boolean build( List<ProjectBuildingResult> results, List<InterimResult> interimResults,
-                           Map<String, MavenProject> projectIndex, List<File> pomFiles, Set<File> aggregatorFiles,
+                           Map<File, MavenProject> projectIndex, List<File> pomFiles, Set<File> aggregatorFiles,
                            boolean root, boolean recursive, InternalConfig config,
                            ReactorModelPool.Builder poolBuilder )
     {
@@ -467,20 +446,19 @@ public class DefaultProjectBuilder
 
     @SuppressWarnings( "checkstyle:parameternumber" )
     private boolean build( List<ProjectBuildingResult> results, List<InterimResult> interimResults,
-                           Map<String, MavenProject> projectIndex, File pomFile, Set<File> aggregatorFiles,
+                           Map<File, MavenProject> projectIndex, File pomFile, Set<File> aggregatorFiles,
                            boolean isRoot, boolean recursive, InternalConfig config,
                            ReactorModelPool.Builder poolBuilder )
     {
         boolean noErrors = true;
 
-        ModelBuildingRequest request = getModelBuildingRequest( config );
-
         MavenProject project = new MavenProject();
         project.setFile( pomFile );
 
-        request.setPomFile( pomFile );
-        request.setTwoPhaseBuilding( true );
-        request.setLocationTracking( true );
+        ModelBuildingRequest request = getModelBuildingRequest( config )
+                        .setPomFile( pomFile )
+                        .setTwoPhaseBuilding( true )
+                        .setLocationTracking( true );
 
         DefaultModelBuildingListener listener =
             new DefaultModelBuildingListener( project, projectBuildingHelper, config.request );
@@ -494,7 +472,7 @@ public class DefaultProjectBuilder
         catch ( ModelBuildingException e )
         {
             result = e.getResult();
-            if ( result == null || result.getEffectiveModel() == null )
+            if ( result == null || result.getFileModel() == null )
             {
                  results.add( new DefaultProjectBuildingResult( e.getModelId(), pomFile, e.getProblems() ) );
 
@@ -505,32 +483,17 @@ public class DefaultProjectBuilder
             noErrors = false;
         }
 
-        Model model = result.getEffectiveModel();
-        
-        poolBuilder.put( model.getPomFile().toPath(),  result.getRawModel() );
-        
-        try
-        {
-            // first pass: build without building parent.
-            initProject( project, projectIndex, false, result, new HashMap<>( 0 ), config.request );
-        }
-        catch ( InvalidArtifactRTException iarte )
-        {
-            result.getProblems().add( new DefaultModelProblem( null, ModelProblem.Severity.ERROR, null, model, -1, -1,
-                  iarte ) );
-        }
+        Model model = result.getFileModel().clone();
 
-        projectIndex.put( result.getModelIds().get( 0 ), project );
+        poolBuilder.put( model.getPomFile().toPath(),  model );
 
         InterimResult interimResult = new InterimResult( pomFile, request, result, listener, isRoot );
         interimResults.add( interimResult );
 
-        if ( recursive && !model.getModules().isEmpty() )
+        if ( recursive )
         {
             File basedir = pomFile.getParentFile();
-
             List<File> moduleFiles = new ArrayList<>();
-
             for ( String module : model.getModules() )
             {
                 if ( StringUtils.isEmpty( module ) )
@@ -609,6 +572,8 @@ public class DefaultProjectBuilder
             }
         }
 
+        projectIndex.put( pomFile, project );
+
         return noErrors;
     }
 
@@ -640,7 +605,7 @@ public class DefaultProjectBuilder
     }
 
     private boolean build( List<ProjectBuildingResult> results, List<MavenProject> projects,
-                           Map<String, MavenProject> projectIndex, List<InterimResult> interimResults,
+                           Map<File, MavenProject> projectIndex, List<InterimResult> interimResults,
                            ProjectBuildingRequest request, Map<File, Boolean> profilesXmls,
                            RepositorySystemSession session )
     {
@@ -685,12 +650,14 @@ public class DefaultProjectBuilder
             catch ( ModelBuildingException e )
             {
                 DefaultProjectBuildingResult result = null;
-                if ( project == null )
+                if ( project == null || interimResult.result.getEffectiveModel() == null )
                 {
                     result = new DefaultProjectBuildingResult( e.getModelId(), interimResult.pomFile, e.getProblems() );
                 }
                 else
                 {
+                    project.setModel( interimResult.result.getEffectiveModel() );
+
                     result = new DefaultProjectBuildingResult( project, e.getProblems(), null );
                 }
                 results.add( result );
@@ -703,15 +670,14 @@ public class DefaultProjectBuilder
     }
 
     @SuppressWarnings( "checkstyle:methodlength" )
-    private void initProject( MavenProject project, Map<String, MavenProject> projects,
+    private void initProject( MavenProject project, Map<File, MavenProject> projects,
                               boolean buildParentIfNotExisting, ModelBuildingResult result,
                               Map<File, Boolean> profilesXmls, ProjectBuildingRequest projectBuildingRequest )
     {
         Model model = result.getEffectiveModel();
 
         project.setModel( model );
-        project.setOriginalModel( result.getRawModel() );
-        project.setFile( model.getPomFile() );
+        project.setOriginalModel( result.getFileModel() );
 
         initParent( project, projects, buildParentIfNotExisting, result, projectBuildingRequest );
 
@@ -938,7 +904,7 @@ public class DefaultProjectBuilder
         }
     }
 
-    private void initParent( MavenProject project, Map<String, MavenProject> projects, boolean buildParentIfNotExisting,
+    private void initParent( MavenProject project, Map<File, MavenProject> projects, boolean buildParentIfNotExisting,
                              ModelBuildingResult result, ProjectBuildingRequest projectBuildingRequest )
     {
         Model parentModel = result.getModelIds().size() > 1 && !result.getModelIds().get( 1 ).isEmpty()
@@ -957,7 +923,7 @@ public class DefaultProjectBuilder
             // org.apache.maven.its.mng4834:parent:0.1
             String parentModelId = result.getModelIds().get( 1 );
             File parentPomFile = result.getRawModel( parentModelId ).getPomFile();
-            MavenProject parent = projects.get( parentModelId );
+            MavenProject parent = projects.get( parentPomFile );
             if ( parent == null && buildParentIfNotExisting )
             {
                 //
@@ -1096,15 +1062,21 @@ public class DefaultProjectBuilder
 
         private final ReactorModelCache modelCache;
 
-        InternalConfig( ProjectBuildingRequest request, ReactorModelPool modelPool, ReactorModelCache modelCache )
+        private final TransformerContextBuilder transformerContextBuilder;
+
+        InternalConfig( ProjectBuildingRequest request, ReactorModelPool modelPool, ReactorModelCache modelCache,
+                        TransformerContextBuilder transformerContextBuilder )
         {
             this.request = request;
             this.modelPool = modelPool;
             this.modelCache = modelCache;
+            this.transformerContextBuilder = transformerContextBuilder;
+
             session =
                 LegacyLocalRepositoryManager.overlay( request.getLocalRepository(), request.getRepositorySession(),
                                                       repoSystem );
             repositories = RepositoryUtils.toRepos( request.getRemoteRepositories() );
+
         }
 
     }
diff --git a/maven-core/src/main/java/org/apache/maven/project/ReactorModelCache.java b/maven-core/src/main/java/org/apache/maven/project/ReactorModelCache.java
index 72c25c7..20e57b9 100644
--- a/maven-core/src/main/java/org/apache/maven/project/ReactorModelCache.java
+++ b/maven-core/src/main/java/org/apache/maven/project/ReactorModelCache.java
@@ -80,13 +80,7 @@ class ReactorModelCache
             this.artifactId = ( artifactId != null ) ? artifactId : "";
             this.version = ( version != null ) ? version : "";
             this.tag = ( tag != null ) ? tag : "";
-
-            int hash = 17;
-            hash = hash * 31 + this.groupId.hashCode();
-            hash = hash * 31 + this.artifactId.hashCode();
-            hash = hash * 31 + this.version.hashCode();
-            hash = hash * 31 + this.tag.hashCode();
-            hashCode = hash;
+            this.hashCode = Objects.hash( groupId, artifactId, version, tag );
         }
 
         @Override
@@ -113,7 +107,6 @@ class ReactorModelCache
         {
             return hashCode;
         }
-
     }
     
     private static final class SourceCacheKey
diff --git a/maven-core/src/main/java/org/apache/maven/xml/internal/DefaultConsumerPomXMLFilterFactory.java b/maven-core/src/main/java/org/apache/maven/xml/internal/DefaultConsumerPomXMLFilterFactory.java
index aa68680..b27a87b 100644
--- a/maven-core/src/main/java/org/apache/maven/xml/internal/DefaultConsumerPomXMLFilterFactory.java
+++ b/maven-core/src/main/java/org/apache/maven/xml/internal/DefaultConsumerPomXMLFilterFactory.java
@@ -9,7 +9,7 @@ package org.apache.maven.xml.internal;
  * "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
+ *  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
@@ -19,10 +19,7 @@ package org.apache.maven.xml.internal;
  * under the License.
  */
 
-import java.util.Optional;
-
 import org.apache.maven.model.building.DefaultBuildPomXMLFilterFactory;
-import org.apache.maven.model.building.TransformerContext;
 import org.apache.maven.xml.sax.filter.ConsumerPomXMLFilterFactory;
 
 /**
@@ -34,32 +31,8 @@ import org.apache.maven.xml.sax.filter.ConsumerPomXMLFilterFactory;
  */
 public class DefaultConsumerPomXMLFilterFactory extends ConsumerPomXMLFilterFactory
 {
-    private final TransformerContext context;
-    
     public DefaultConsumerPomXMLFilterFactory( DefaultBuildPomXMLFilterFactory buildPomXMLFilterFactory )
     {
         super( buildPomXMLFilterFactory );
-        this.context = buildPomXMLFilterFactory.getContext();
-    }
-    
-    @Override
-    protected Optional<String> getChangelist()
-    {
-        return Optional.ofNullable( context.getUserProperty( "changelist" ) );
-    }
-
-    @Override
-    protected Optional<String> getRevision()
-    {
-        return Optional.ofNullable( context.getUserProperty( "revision" ) );
     }
-
-    @Override
-    protected Optional<String> getSha1()
-    {
-        return Optional.ofNullable( context.getUserProperty( "sha1" ) );
-    }
-
-
-
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java
index 9766b8d..1119623 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java
@@ -58,7 +58,7 @@ class BuildModelSourceTransformer extends AbstractModelSourceTransformer
         throws TransformerConfigurationException, SAXException, ParserConfigurationException
     {
         BuildPomXMLFilterFactory buildPomXMLFilterFactory =
-            new DefaultBuildPomXMLFilterFactory( context, lexicalHandlerConsumer );
+            new DefaultBuildPomXMLFilterFactory( context, lexicalHandlerConsumer, false );
 
         return buildPomXMLFilterFactory.get( pomFile );
     }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java
index cf095e0..2424874 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java
@@ -32,32 +32,35 @@ import org.apache.maven.xml.sax.filter.RelativeProject;
 import org.xml.sax.ext.LexicalHandler;
 
 /**
- * 
+ * A BuildPomXMLFilterFactory which is context aware
+ *
  * @author Robert Scholte
  * @since 4.0.0
  */
 public class DefaultBuildPomXMLFilterFactory extends BuildPomXMLFilterFactory
 {
     private final TransformerContext context;
-    
+
+    /**
+     *
+     * @param context a set of data to extract values from as required for the build pom
+     * @param lexicalHandlerConsumer the lexical handler consumer
+     * @param consume {@code true} if this factory is being used for creating the consumer pom, otherwise {@code false}
+     */
     public DefaultBuildPomXMLFilterFactory( TransformerContext context,
-                                            Consumer<LexicalHandler> lexicalHandlerConsumer )
+                                            Consumer<LexicalHandler> lexicalHandlerConsumer, 
+                                            boolean consume )
     {
-        super( lexicalHandlerConsumer );
+        super( lexicalHandlerConsumer, consume );
         this.context = context;
     }
-    
-    public final TransformerContext getContext()
-    {
-        return context;
-    }
-    
+
     @Override
     protected Function<Path, Optional<RelativeProject>> getRelativePathMapper()
     {
         return p -> Optional.ofNullable( context.getRawModel( p ) ).map( m -> toRelativeProject( m ) );
     }
-    
+
     @Override
     protected BiFunction<String, String, String> getDependencyKeyToVersionMapper()
     {
@@ -66,6 +69,24 @@ public class DefaultBuildPomXMLFilterFactory extends BuildPomXMLFilterFactory
                             .orElse( null );
     }
 
+    @Override
+    protected Optional<String> getChangelist()
+    {
+        return Optional.ofNullable( context.getUserProperty( "changelist" ) );
+    }
+
+    @Override
+    protected Optional<String> getRevision()
+    {
+        return Optional.ofNullable( context.getUserProperty( "revision" ) );
+    }
+
+    @Override
+    protected Optional<String> getSha1()
+    {
+        return Optional.ofNullable( context.getUserProperty( "sha1" ) );
+    }
+
     private static RelativeProject toRelativeProject( final Model m )
     {
         String groupId = m.getGroupId();
@@ -82,7 +103,7 @@ public class DefaultBuildPomXMLFilterFactory extends BuildPomXMLFilterFactory
 
         return new RelativeProject( groupId, m.getArtifactId(), version );
     }
-    
+
     private static String toVersion( final Model m )
     {
         String version = m.getVersion();
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java
index 8b31908..e12e566 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java
@@ -25,16 +25,21 @@ import static org.apache.maven.model.building.Result.newResult;
 import org.apache.maven.artifact.versioning.ArtifactVersion;
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 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.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -69,6 +74,7 @@ import org.apache.maven.model.composition.DependencyManagementImporter;
 import org.apache.maven.model.inheritance.InheritanceAssembler;
 import org.apache.maven.model.interpolation.ModelInterpolator;
 import org.apache.maven.model.io.ModelParseException;
+import org.apache.maven.model.io.ModelReader;
 import org.apache.maven.model.management.DependencyManagementInjector;
 import org.apache.maven.model.management.PluginManagementInjector;
 import org.apache.maven.model.merge.ModelMerger;
@@ -117,7 +123,7 @@ public class DefaultModelBuilder
 
     @Inject
     private ModelUrlNormalizer modelUrlNormalizer;
-    
+
     @Inject
     private SuperPomProvider superPomProvider;
 
@@ -151,7 +157,7 @@ public class DefaultModelBuilder
 
     @Inject
     private ReportingConverter reportingConverter;
-    
+
     private ModelMerger modelMerger = new FileToRawModelMerger();
 
     public DefaultModelBuilder setModelProcessor( ModelProcessor modelProcessor )
@@ -255,7 +261,13 @@ public class DefaultModelBuilder
         this.reportingConverter = reportingConverter;
         return this;
     }
-    
+
+    @Override
+    public DefaultTransformerContextBuilder newTransformerContextBuilder()
+    {
+        return new DefaultTransformerContextBuilder();
+    }
+
     @Override
     public ModelBuildingResult build( ModelBuildingRequest request )
         throws ModelBuildingException
@@ -272,6 +284,33 @@ public class DefaultModelBuilder
 
         DefaultModelProblemCollector problems = new DefaultModelProblemCollector( result );
 
+        // read and validate raw model
+        Model fileModel = readFileModel( request, problems );
+
+        request.setFileModel( fileModel );
+        result.setFileModel( fileModel );
+
+        activateFileModel( request, result, problems );
+
+        if ( !request.isTwoPhaseBuilding() )
+        {
+            return build( request, result, importIds );
+        }
+        else if ( hasModelErrors( problems ) )
+        {
+            throw problems.newModelBuildingException();
+        }
+
+        return result;
+    }
+
+    private void activateFileModel( final ModelBuildingRequest request, final DefaultModelBuildingResult result,
+                          DefaultModelProblemCollector problems )
+        throws ModelBuildingException
+    {
+        Model inputModel = request.getFileModel();
+        problems.setRootModel( inputModel );
+
         // profile activation
         DefaultProfileActivationContext profileActivationContext = getProfileActivationContext( request );
 
@@ -292,47 +331,89 @@ public class DefaultModelBuilder
             profileActivationContext.setUserProperties( profileProps );
         }
 
-        // read and validate raw model
-        Model inputModel = request.getRawModel();
-        if ( inputModel == null )
+        profileActivationContext.setProjectProperties( inputModel.getProperties() );
+        problems.setSource( inputModel );
+        List<Profile> activePomProfiles = profileSelector.getActiveProfiles( inputModel.getProfiles(),
+                                                                             profileActivationContext, problems );
+
+        // model normalization
+        problems.setSource( inputModel );
+        modelNormalizer.mergeDuplicates( inputModel, request, problems );
+
+        Map<String, Activation> interpolatedActivations = getProfileActivations( inputModel, false );
+        injectProfileActivations( inputModel, interpolatedActivations );
+
+        // profile injection
+        for ( Profile activeProfile : activePomProfiles )
+        {
+            profileInjector.injectProfile( inputModel, activeProfile, request, problems );
+        }
+
+        for ( Profile activeProfile : activeExternalProfiles )
         {
-            inputModel = readModel( request.getModelSource(), request.getPomFile(), request, problems );
+            profileInjector.injectProfile( inputModel, activeProfile, request, problems );
         }
+    }
+
+    @SuppressWarnings( "checkstyle:methodlength" )
+    private Model readEffectiveModel( final ModelBuildingRequest request, final DefaultModelBuildingResult result,
+                          DefaultModelProblemCollector problems )
+        throws ModelBuildingException
+    {
+        Model inputModel =
+            readRawModel( request, problems );
 
         problems.setRootModel( inputModel );
 
         ModelData resultData = new ModelData( request.getModelSource(), inputModel );
         ModelData superData = new ModelData( null, getSuperModel() );
 
+        // profile activation
+        DefaultProfileActivationContext profileActivationContext = getProfileActivationContext( request );
+
+        List<Profile> 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<String> parentIds = new LinkedHashSet<>();
-        List<ModelData> lineage = new ArrayList<>();
+
+        List<Model> lineage = new ArrayList<>();
 
         for ( ModelData currentData = resultData; currentData != null; )
         {
-            lineage.add( currentData );
+            String modelId = currentData.getId();
+            result.addModelId( modelId );
 
             Model rawModel = currentData.getModel();
-            currentData.setRawModel( rawModel );
+            result.setRawModel( modelId, rawModel );
+
+            profileActivationContext.setProjectProperties( rawModel.getProperties() );
+            problems.setSource( rawModel );
+            List<Profile> activePomProfiles = profileSelector.getActiveProfiles( rawModel.getProfiles(),
+                                                                                 profileActivationContext, problems );
+            result.setActivePomProfiles( modelId, activePomProfiles );
 
             Model tmpModel = rawModel.clone();
-            currentData.setModel( tmpModel );
 
             problems.setSource( tmpModel );
 
             // model normalization
             modelNormalizer.mergeDuplicates( tmpModel, request, problems );
 
-            profileActivationContext.setProjectProperties( tmpModel.getProperties() );
-
-            List<Profile> activePomProfiles = profileSelector.getActiveProfiles( rawModel.getProfiles(),
-                                                                                 profileActivationContext, problems );
-            currentData.setActiveProfiles( activePomProfiles );
-
-            Map<String, Activation> interpolatedActivations = getProfileActivations( rawModel, false );
+            Map<String, Activation> interpolatedActivations = getProfileActivations( tmpModel, false );
             injectProfileActivations( tmpModel, interpolatedActivations );
 
             // profile injection
-            for ( Profile activeProfile : activePomProfiles )
+            for ( Profile activeProfile : result.getActivePomProfiles( modelId ) )
             {
                 profileInjector.injectProfile( tmpModel, activeProfile, request, problems );
             }
@@ -343,8 +424,11 @@ public class DefaultModelBuilder
                 {
                     profileInjector.injectProfile( tmpModel, activeProfile, request, problems );
                 }
+                result.setEffectiveModel( tmpModel );
             }
 
+            lineage.add( tmpModel );
+
             if ( currentData == superData )
             {
                 break;
@@ -352,36 +436,19 @@ public class DefaultModelBuilder
 
             configureResolver( request.getModelResolver(), tmpModel, problems );
 
-            ModelData parentData = readParent( tmpModel, currentData.getSource(), request, problems );
+            ModelData parentData =
+                readParent( currentData.getModel(), currentData.getSource(), request, result, problems );
 
             if ( parentData == null )
             {
                 currentData = superData;
             }
-            else if ( currentData == resultData )
-            { // First iteration - add initial id after version resolution.
-                currentData.setGroupId( currentData.getRawModel().getGroupId() == null ? parentData.getGroupId()
-                                                                                      : currentData.getRawModel()
-                                                                                          .getGroupId() );
-
-                currentData.setVersion( currentData.getRawModel().getVersion() == null ? parentData.getVersion()
-                                                                                      : currentData.getRawModel()
-                                                                                          .getVersion() );
-
-                currentData.setArtifactId( currentData.getRawModel().getArtifactId() );
-                parentIds.add( currentData.getId() );
-                // Reset - only needed for 'getId'.
-                currentData.setGroupId( null );
-                currentData.setArtifactId( null );
-                currentData.setVersion( null );
-                currentData = parentData;
-            }
             else if ( !parentIds.add( parentData.getId() ) )
             {
                 StringBuilder message = new StringBuilder( "The parents form a cycle: " );
-                for ( String modelId : parentIds )
+                for ( String parentId : parentIds )
                 {
-                    message.append( modelId ).append( " -> " );
+                    message.append( parentId ).append( " -> " );
                 }
                 message.append( parentData.getId() );
 
@@ -396,86 +463,50 @@ public class DefaultModelBuilder
             }
         }
 
-        problems.setSource( inputModel );
+        problems.setSource( result.getRawModel() );
         checkPluginVersions( lineage, request, problems );
 
         // inheritance assembly
         assembleInheritance( lineage, request, problems );
 
-        Model resultModel = resultData.getModel();
+        Model resultModel = lineage.get( 0 );
+
+        // consider caching inherited model
 
         problems.setSource( resultModel );
         problems.setRootModel( resultModel );
 
         // model interpolation
         resultModel = interpolateModel( resultModel, request, problems );
-        resultData.setModel( resultModel );
-
-        if ( resultModel.getParent() != null )
-        {
-            final ModelData parentData = lineage.get( 1 );
-            if ( parentData.getVersion() == null || parentData.getVersion().contains( "${" ) )
-            {
-                final Model interpolatedParent = interpolateModel( parentData.getModel(), request, problems );
-                // parentData.setModel( interpolatedParent );
-                parentData.setVersion( interpolatedParent.getVersion() );
-            }
-        }
 
         // url normalization
         modelUrlNormalizer.normalize( resultModel, request );
 
-        // Now the fully interpolated model is available: reconfigure the resolver
-        configureResolver( request.getModelResolver(), resultModel, problems, true );
-
-        resultData.setGroupId( resultModel.getGroupId() );
-        resultData.setArtifactId( resultModel.getArtifactId() );
-        resultData.setVersion( resultModel.getVersion() );
-
-        if ( request.getPomFile() != null )
-        {
-            intoCache( request.getModelCache(), new FileModelSource( request.getPomFile() ), ModelCacheTag.RAW,
-                      resultData );
-        }
-        else
-        {
-            intoCache( request.getModelCache(), request.getModelSource(), ModelCacheTag.RAW, resultData );
-        }
-
         result.setEffectiveModel( resultModel );
-        
-        for ( ModelData currentData : lineage )
-        {
-            String modelId = ( currentData != superData ) ? currentData.getId() : "";
-
-            result.addModelId( modelId );
-            result.setActivePomProfiles( modelId, currentData.getActiveProfiles() );
-            result.setRawModel( modelId, currentData.getRawModel() );
-        }
 
-        if ( !request.isTwoPhaseBuilding() )
-        {
-            build( request, result, importIds );
-        }
+        // Now the fully interpolated model is available: reconfigure the resolver
+        configureResolver( request.getModelResolver(), resultModel, problems, true );
 
-        return result;
+        return resultModel;
     }
 
     @Override
-    public ModelBuildingResult build( ModelBuildingRequest request, ModelBuildingResult result )
+    public ModelBuildingResult build( final ModelBuildingRequest request, final ModelBuildingResult result )
         throws ModelBuildingException
     {
         return build( request, result, new LinkedHashSet<>() );
     }
 
-    private ModelBuildingResult build( ModelBuildingRequest request, ModelBuildingResult result,
+    private ModelBuildingResult build( final ModelBuildingRequest request, final ModelBuildingResult phaseOneResult,
                                        Collection<String> imports )
         throws ModelBuildingException
     {
-        // phase 2
-        Model resultModel = result.getEffectiveModel();
+        DefaultModelBuildingResult result = asDefaultModelBuildingResult( phaseOneResult );
 
         DefaultModelProblemCollector problems = new DefaultModelProblemCollector( result );
+
+        // phase 2
+        Model resultModel = readEffectiveModel( request, result, problems );
         problems.setSource( resultModel );
         problems.setRootModel( resultModel );
 
@@ -529,6 +560,18 @@ public class DefaultModelBuilder
         return result;
     }
 
+    private DefaultModelBuildingResult asDefaultModelBuildingResult( ModelBuildingResult phaseOneResult )
+    {
+        if ( phaseOneResult instanceof DefaultModelBuildingResult )
+        {
+            return (DefaultModelBuildingResult) phaseOneResult;
+        }
+        else
+        {
+            return new DefaultModelBuildingResult( phaseOneResult );
+        }
+    }
+
     @Override
     public Result<? extends Model> buildRawModel( File pomFile, int validationLevel, boolean locationTracking )
     {
@@ -538,7 +581,7 @@ public class DefaultModelBuilder
             new DefaultModelProblemCollector( new DefaultModelBuildingResult() );
         try
         {
-            return newResult( readModel( null, pomFile, request, collector ), collector.getProblems() );
+            return newResult( readFileModel( request, collector ), collector.getProblems() );
         }
         catch ( ModelBuildingException e )
         {
@@ -547,24 +590,15 @@ public class DefaultModelBuilder
     }
 
     @SuppressWarnings( "checkstyle:methodlength" )
-    private Model readModel( ModelSource modelSource, File pomFile, ModelBuildingRequest request,
-                             DefaultModelProblemCollector problems )
+    private Model readFileModel( ModelBuildingRequest request,
+                                 DefaultModelProblemCollector problems )
         throws ModelBuildingException
     {
-        if ( modelSource == null )
+        ModelSource modelSource = request.getModelSource();
+        Model model = getModelFromCache( modelSource, request.getModelCache() );
+        if ( model != null )
         {
-            modelSource =
-                new FileModelSource( Objects.requireNonNull( pomFile, "neither pomFile nor modelSource can be null" ) );
-        }
-
-        Model model;
-        if ( pomFile == null )
-        {
-            model = getModelFromCache( modelSource, request.getModelCache() );
-            if ( model != null )
-            {
-                return model;
-            }
+            return model;
         }
 
         problems.setSource( modelSource.getLocation() );
@@ -577,7 +611,7 @@ public class DefaultModelBuilder
             options.put( ModelProcessor.SOURCE, modelSource );
 
             InputSource source;
-            if ( request.isLocationTracking() ) 
+            if ( request.isLocationTracking() )
             {
                 source = (InputSource) options.computeIfAbsent( ModelProcessor.INPUT_SOURCE, k -> new InputSource() );
             }
@@ -609,7 +643,7 @@ public class DefaultModelBuilder
                     throw e;
                 }
 
-                if ( pomFile != null )
+                if ( modelSource instanceof FileModelSource )
                 {
                     problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.V20 )
                         .setMessage( "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage() )
@@ -655,69 +689,106 @@ public class DefaultModelBuilder
                 .setMessage( "Non-readable POM " + modelSource.getLocation() + ": " + msg ).setException( e ) );
             throw problems.newModelBuildingException();
         }
-        if ( pomFile != null )
-        {
-            model.setPomFile( pomFile );
-        }
-        else if ( modelSource instanceof FileModelSource )
+
+        if ( modelSource instanceof FileModelSource )
         {
             model.setPomFile( ( (FileModelSource) modelSource ).getFile() );
         }
         problems.setSource( model );
 
         modelValidator.validateFileModel( model, request, problems );
-        request.setFileModel( model );
-        
-        if ( Features.buildConsumer().isActive() && pomFile != null )
+
+        if ( hasFatalErrors( problems ) )
+        {
+            throw problems.newModelBuildingException();
+        }
+
+        intoCache( request.getModelCache(), modelSource, ModelCacheTag.FILE, model );
+        if ( modelSource instanceof FileModelSource )
         {
+            if ( request.getTransformerContextBuilder() instanceof DefaultTransformerContextBuilder )
+            {
+                DefaultTransformerContextBuilder contextBuilder =
+                        (DefaultTransformerContextBuilder) request.getTransformerContextBuilder();
+                contextBuilder.putSource( getGroupId( model ), model.getArtifactId(), modelSource );
+            }
+        }
+
+        return model;
+    }
+
+    private Model readRawModel( ModelBuildingRequest request, DefaultModelProblemCollector problems )
+        throws ModelBuildingException
+    {
+        ModelSource modelSource = request.getModelSource();
+
+        ModelData cachedData = fromCache( request.getModelCache(), modelSource, ModelCacheTag.RAW );
+        if ( cachedData != null )
+        {
+          return cachedData.getModel();
+        }
+
+        Model rawModel;
+        if ( Features.buildConsumer().isActive() && modelSource instanceof FileModelSource )
+        {
+            rawModel = readFileModel( request, problems );
+            File pomFile = ( (FileModelSource) modelSource ).getFile();
+
+            TransformerContext context = null;
+            if ( request.getTransformerContextBuilder() != null )
+            {
+                context = request.getTransformerContextBuilder().initialize( request, problems );
+            }
+
             try
             {
-                Model rawModel =
-                    modelProcessor.read( pomFile,
-                               Collections.singletonMap( "transformerContext", request.getTransformerContext() ) );
-
-                model.setPomFile( pomFile );
-                
-                // model with locationTrackers, required for proper feedback during validations
-                model = request.getFileModel().clone();
-                
+                // must implement TransformContext, but should use request to access system properties/modelcache
+                Model transformedFileModel = modelProcessor.read( pomFile,
+                   Collections.singletonMap( ModelReader.TRANSFORMER_CONTEXT, context ) );
+
+                // rawModel with locationTrackers, required for proper feedback during validations
+
                 // Apply enriched data
-                modelMerger.merge( model, rawModel, false, null );
+                modelMerger.merge( rawModel, transformedFileModel, false, null );
             }
             catch ( IOException e )
             {
                 problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.V37 ).setException( e ) );
             }
         }
+        else if ( request.getFileModel() == null )
+        {
+            rawModel = readFileModel( request, problems );
+        }
+        else
+        {
+            rawModel = request.getFileModel().clone();
+        }
 
-        modelValidator.validateRawModel( model, request, problems );
+        modelValidator.validateRawModel( rawModel, request, problems );
 
         if ( hasFatalErrors( problems ) )
         {
             throw problems.newModelBuildingException();
         }
 
-        if ( pomFile != null )
-        {
-            intoCache( request.getModelCache(), modelSource, ModelCacheTag.FILEMODEL, model );
-        }
+        String groupId = getGroupId( rawModel );
+        String artifactId = rawModel.getArtifactId();
+        String version = getVersion( rawModel );
 
-        String groupId = getGroupId( model );
-        String artifactId = model.getArtifactId();
-        String version = getVersion( model );
-
-        ModelData modelData = new ModelData( modelSource, model, groupId, artifactId, version );
+        ModelData modelData = new ModelData( modelSource, rawModel, groupId, artifactId, version );
         intoCache( request.getModelCache(), groupId, artifactId, version, ModelCacheTag.RAW, modelData );
+        intoCache( request.getModelCache(), modelSource, ModelCacheTag.RAW, modelData );
 
-        return model;
+        return rawModel;
     }
 
-    private Model getModelFromCache( ModelSource modelSource, ModelCache cache )
+    private Model getModelFromCache( Source source, ModelCache cache )
     {
         Model model;
-        if ( modelSource instanceof ArtifactModelSource )
+        if ( source instanceof ArtifactModelSource )
         {
-            ArtifactModelSource artifactModelSource = ( ArtifactModelSource ) modelSource;
+            ArtifactModelSource artifactModelSource = ( ArtifactModelSource ) source;
             ModelData modelData = fromCache( cache, artifactModelSource.getGroupId(),
                                             artifactModelSource.getArtifactId(),
                                             artifactModelSource.getVersion(), ModelCacheTag.RAW );
@@ -725,19 +796,14 @@ public class DefaultModelBuilder
             {
                 model = modelData.getModel();
             }
-            else 
+            else
             {
                 model = null;
             }
         }
         else
         {
-            model = fromCache( cache, modelSource, ModelCacheTag.FILEMODEL );
-            
-            if ( model != null )
-            {
-                model = model.clone();
-            }
+            model = fromCache( cache, source, ModelCacheTag.FILE );
         }
         return model;
     }
@@ -752,13 +818,13 @@ public class DefaultModelBuilder
         return groupId;
     }
 
-    private String getVersion( Model model ) 
+    private String getVersion( Model model )
     {
         String version = model.getVersion();
         if ( version == null && model.getParent() != null )
         {
             version = model.getParent().getVersion();
-        } 
+        }
         return version;
     }
 
@@ -807,7 +873,7 @@ public class DefaultModelBuilder
         }
     }
 
-    private void checkPluginVersions( List<ModelData> lineage, ModelBuildingRequest request,
+    private void checkPluginVersions( List<Model> lineage, ModelBuildingRequest request,
                                       ModelProblemCollector problems )
     {
         if ( request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
@@ -821,7 +887,7 @@ public class DefaultModelBuilder
 
         for ( int i = lineage.size() - 1; i >= 0; i-- )
         {
-            Model model = lineage.get( i ).getModel();
+            Model model = lineage.get( i );
             Build build = model.getBuild();
             if ( build != null )
             {
@@ -859,13 +925,13 @@ public class DefaultModelBuilder
         }
     }
 
-    private void assembleInheritance( List<ModelData> lineage, ModelBuildingRequest request,
+    private void assembleInheritance( List<Model> lineage, ModelBuildingRequest request,
                                       ModelProblemCollector problems )
     {
         for ( int i = lineage.size() - 2; i >= 0; i-- )
         {
-            Model parent = lineage.get( i + 1 ).getModel();
-            Model child = lineage.get( i ).getModel();
+            Model parent = lineage.get( i + 1 );
+            Model child = lineage.get( i );
             inheritanceAssembler.assembleModelInheritance( child, parent, request, problems );
         }
     }
@@ -940,7 +1006,7 @@ public class DefaultModelBuilder
                 problems.add( mpcr );
             }
 
-            
+
         }
         interpolatedModel.setPomFile( model.getPomFile() );
 
@@ -950,8 +1016,8 @@ public class DefaultModelBuilder
         return interpolatedModel;
     }
 
-    private ModelData readParent( Model childModel, ModelSource childSource, ModelBuildingRequest request,
-                                  DefaultModelProblemCollector problems )
+    private ModelData readParent( Model childModel, Source childSource, ModelBuildingRequest request,
+                                  ModelBuildingResult result, DefaultModelProblemCollector problems )
         throws ModelBuildingException
     {
         ModelData parentData = null;
@@ -960,18 +1026,18 @@ public class DefaultModelBuilder
 
         if ( parent != null )
         {
-            ModelSource expectedParentSource = getParentPomFile( childModel, childSource );
+            Source expectedParentSource = getParentPomFile( childModel, childSource );
 
             if ( expectedParentSource != null )
             {
-                ModelData candidateData = readParentLocally( childModel, childSource, request, problems );
+                ModelData candidateData = readParentLocally( childModel, childSource, request, result, problems );
 
                 if ( candidateData != null )
                 {
                     /*
-                     * NOTE: This is a sanity check of the cache hit. If the cached parent POM was locally resolved, 
+                     * NOTE: This is a sanity check of the cache hit. If the cached parent POM was locally resolved,
                      * the child's GAV should match with that parent, too. If it doesn't, we ignore the cache and
-                     * resolve externally, to mimic the behavior if the cache didn't exist in the first place. 
+                     * resolve externally, to mimic the behavior if the cache didn't exist in the first place.
                      * Otherwise, the cache would obscure a bad POM.
                      */
                     try
@@ -995,11 +1061,11 @@ public class DefaultModelBuilder
 
             if ( parentData == null )
             {
-                ModelData candidateData = fromCache( request.getModelCache(), 
+                ModelData candidateData = fromCache( request.getModelCache(),
                                                      parent.getGroupId(), parent.getArtifactId(),
                                                      parent.getVersion(), ModelCacheTag.RAW );
 
-                
+
                 if ( candidateData != null && candidateData.getSource() instanceof ArtifactModelSource )
                 {
                     // ArtifactModelSource means repositorySource
@@ -1007,15 +1073,15 @@ public class DefaultModelBuilder
                 }
                 else
                 {
-                    parentData = readParentExternally( childModel, request, problems );
-                    
-                    intoCache( request.getModelCache(), 
+                    parentData = readParentExternally( childModel, request, result, problems );
+
+                    intoCache( request.getModelCache(),
                               parentData.getGroupId(), parentData.getArtifactId(),
                               parentData.getVersion(), ModelCacheTag.RAW, parentData );
                 }
             }
-            
-            if ( parentData != null ) 
+
+            if ( parentData != null )
             {
                 Model parentModel = parentData.getModel();
 
@@ -1032,8 +1098,8 @@ public class DefaultModelBuilder
         return parentData;
     }
 
-    private ModelData readParentLocally( Model childModel, ModelSource childSource, ModelBuildingRequest request,
-                                         DefaultModelProblemCollector problems )
+    private ModelData readParentLocally( Model childModel, Source childSource, ModelBuildingRequest request,
+                                         ModelBuildingResult result, DefaultModelProblemCollector problems )
         throws ModelBuildingException
     {
         final Parent parent = childModel.getParent();
@@ -1049,7 +1115,16 @@ public class DefaultModelBuilder
                 return null;
             }
 
-            candidateModel = readModel( candidateSource, null, request, problems );
+            ModelBuildingRequest candidateBuildRequest = new FilterModelBuildingRequest( request )
+            {
+                  @Override
+                public ModelSource getModelSource()
+                {
+                    return candidateSource;
+                }
+            };
+
+            candidateModel = readRawModel( candidateBuildRequest, problems );
         }
         else
         {
@@ -1083,11 +1158,6 @@ public class DefaultModelBuilder
             groupId = candidateModel.getParent().getGroupId();
         }
         String artifactId = candidateModel.getArtifactId();
-        String version = candidateModel.getVersion();
-        if ( version == null && candidateModel.getParent() != null )
-        {
-            version = candidateModel.getParent().getVersion();
-        }
 
         if ( groupId == null || !groupId.equals( parent.getGroupId() ) || artifactId == null
             || !artifactId.equals( parent.getArtifactId() ) )
@@ -1107,6 +1177,12 @@ public class DefaultModelBuilder
                 .setMessage( buffer.toString() ).setLocation( parent.getLocation( "" ) ) );
             return null;
         }
+
+        String version = candidateModel.getVersion();
+        if ( version == null && candidateModel.getParent() != null )
+        {
+            version = candidateModel.getParent().getVersion();
+        }
         if ( version != null && parent.getVersion() != null && !version.equals( parent.getVersion() ) )
         {
             try
@@ -1161,12 +1237,10 @@ public class DefaultModelBuilder
          * if ( version == null || !version.equals( parent.getVersion() ) ) { return null; }
          */
 
-        ModelData parentData = new ModelData( candidateSource, candidateModel, groupId, artifactId, version );
-
-        return parentData;
+        return new ModelData( candidateSource, candidateModel, groupId, artifactId, version );
     }
 
-    private ModelSource getParentPomFile( Model childModel, ModelSource source )
+    private ModelSource getParentPomFile( Model childModel, Source source )
     {
         if ( !( source instanceof ModelSource2 ) )
         {
@@ -1184,12 +1258,12 @@ public class DefaultModelBuilder
     }
 
     private ModelData readParentExternally( Model childModel, ModelBuildingRequest request,
-                                            DefaultModelProblemCollector problems )
+                                            ModelBuildingResult result, DefaultModelProblemCollector problems )
         throws ModelBuildingException
     {
         problems.setSource( childModel );
 
-        Parent parent = childModel.getParent().clone();
+        Parent parent = childModel.getParent();
 
         String groupId = parent.getGroupId();
         String artifactId = parent.getArtifactId();
@@ -1237,20 +1311,28 @@ public class DefaultModelBuilder
             throw problems.newModelBuildingException();
         }
 
-        ModelBuildingRequest lenientRequest = request;
-        if ( request.getValidationLevel() > ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
+        int validationLevel = Math.min( request.getValidationLevel(), ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 );
+        ModelBuildingRequest lenientRequest = new FilterModelBuildingRequest( request )
         {
-            lenientRequest = new FilterModelBuildingRequest( request )
+            @Override
+            public int getValidationLevel()
             {
-                @Override
-                public int getValidationLevel()
-                {
-                    return ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0;
-                }
-            };
-        }
+                return validationLevel;
+            }
+
+            @Override
+            public ModelSource getModelSource()
+            {
+                return modelSource;
+            }
+            @Override
+            public Model getFileModel()
+            {
+                return null;
+            }
+        };
 
-        Model parentModel = readModel( modelSource, null, lenientRequest, problems );
+        Model parentModel = readRawModel( lenientRequest, problems );
 
         if ( !parent.getVersion().equals( version ) )
         {
@@ -1276,10 +1358,8 @@ public class DefaultModelBuilder
             // MNG-2199: What else to check here ?
         }
 
-        ModelData parentData = new ModelData( modelSource, parentModel, parent.getGroupId(), parent.getArtifactId(),
-                                              parent.getVersion() );
-
-        return parentData;
+        return new ModelData( modelSource, parentModel, parent.getGroupId(), parent.getArtifactId(),
+                              parent.getVersion() );
     }
 
     private Model getSuperModel()
@@ -1486,7 +1566,7 @@ public class DefaultModelBuilder
         }
     }
 
-    private <T> T fromCache( ModelCache modelCache, String groupId, String artifactId, String version,
+    private static <T> T fromCache( ModelCache modelCache, String groupId, String artifactId, String version,
                             ModelCacheTag<T> tag )
     {
         if ( modelCache != null )
@@ -1500,7 +1580,7 @@ public class DefaultModelBuilder
         return null;
     }
 
-    private <T> T fromCache( ModelCache modelCache, Source source, ModelCacheTag<T> tag )
+    private static <T> T fromCache( ModelCache modelCache, Source source, ModelCacheTag<T> tag )
     {
         if ( modelCache != null )
         {
@@ -1564,8 +1644,8 @@ public class DefaultModelBuilder
 
     /**
      * As long as Maven controls the BuildPomXMLFilter, the entities that need merging are known.
-     * All others can simply be copied from source to target to restore the locationTracker 
-     * 
+     * All others can simply be copied from source to target to restore the locationTracker
+     *
      * @author Robert Scholte
      * @since 4.0.0
      */
@@ -1577,7 +1657,7 @@ public class DefaultModelBuilder
         {
             // don't merge
         }
-        
+
 
         @Override
         protected void mergeBuildBase_Resources( BuildBase target, BuildBase source, boolean sourceDominant,
@@ -1585,21 +1665,21 @@ public class DefaultModelBuilder
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeBuildBase_TestResources( BuildBase target, BuildBase source, boolean sourceDominant,
                                                      Map<Object, Object> context )
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeCiManagement_Notifiers( CiManagement target, CiManagement source, boolean sourceDominant,
                                                     Map<Object, Object> context )
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeDependencyManagement_Dependencies( DependencyManagement target, DependencyManagement source,
                                                                boolean sourceDominant, Map<Object, Object> context )
@@ -1608,14 +1688,14 @@ public class DefaultModelBuilder
             target.getDependencies().stream().forEach( t -> mergeDependency( t, sourceIterator.next(), sourceDominant,
                                                                              context ) );
         }
-        
+
         @Override
         protected void mergeDependency_Exclusions( Dependency target, Dependency source, boolean sourceDominant,
                                                    Map<Object, Object> context )
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeModel_Contributors( Model target, Model source, boolean sourceDominant,
                                                 Map<Object, Object> context )
@@ -1629,21 +1709,21 @@ public class DefaultModelBuilder
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeModel_Licenses( Model target, Model source, boolean sourceDominant,
                                             Map<Object, Object> context )
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeModel_MailingLists( Model target, Model source, boolean sourceDominant,
                                                 Map<Object, Object> context )
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeModel_Profiles( Model target, Model source, boolean sourceDominant,
                                             Map<Object, Object> context )
@@ -1652,7 +1732,7 @@ public class DefaultModelBuilder
             target.getProfiles().stream().forEach( t -> mergeProfile( t, sourceIterator.next(), sourceDominant,
                                                                       context ) );
         }
-        
+
         @Override
         protected void mergeModelBase_Dependencies( ModelBase target, ModelBase source, boolean sourceDominant,
                                                     Map<Object, Object> context )
@@ -1661,21 +1741,21 @@ public class DefaultModelBuilder
             target.getDependencies().stream().forEach( t -> mergeDependency( t, sourceIterator.next(), sourceDominant,
                                                                              context ) );
         }
-        
+
         @Override
         protected void mergeModelBase_PluginRepositories( ModelBase target, ModelBase source, boolean sourceDominant,
                                                           Map<Object, Object> context )
         {
             target.setPluginRepositories( source.getPluginRepositories() );
         }
-        
+
         @Override
         protected void mergeModelBase_Repositories( ModelBase target, ModelBase source, boolean sourceDominant,
                                                     Map<Object, Object> context )
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergePlugin_Dependencies( Plugin target, Plugin source, boolean sourceDominant,
                                                  Map<Object, Object> context )
@@ -1684,14 +1764,14 @@ public class DefaultModelBuilder
             target.getDependencies().stream().forEach( t -> mergeDependency( t, sourceIterator.next(), sourceDominant,
                                                                              context ) );
         }
-        
+
         @Override
         protected void mergePlugin_Executions( Plugin target, Plugin source, boolean sourceDominant,
                                                Map<Object, Object> context )
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergeReporting_Plugins( Reporting target, Reporting source, boolean sourceDominant,
                                                Map<Object, Object> context )
@@ -1705,7 +1785,7 @@ public class DefaultModelBuilder
         {
             // don't merge
         }
-        
+
         @Override
         protected void mergePluginContainer_Plugins( PluginContainer target, PluginContainer source,
                                                      boolean sourceDominant, Map<Object, Object> context )
@@ -1713,4 +1793,132 @@ public class DefaultModelBuilder
             // don't merge
         }
     }
+
+    /**
+     * Builds up the transformer context.
+     * After the buildplan is ready, the build()-method returns the immutable context useful during distribution.
+     * This is an inner class, as it must be able to call readRawModel()
+     *
+     * @author Robert Scholte
+     * @since 4.0.0
+     */
+    private class DefaultTransformerContextBuilder implements TransformerContextBuilder
+    {
+        private final DefaultTransformerContext context = new DefaultTransformerContext();
+
+        private final Map<DefaultTransformerContext.GAKey, Set<Source>> mappedSources
+                = new ConcurrentHashMap<>( 64 );
+
+        /**
+         * If an interface could be extracted, DefaultModelProblemCollector should be ModelProblemCollectorExt
+         *
+         * @param request
+         * @param collector
+         * @return
+         */
+        @Override
+        public TransformerContext initialize( ModelBuildingRequest request, ModelProblemCollector collector )
+        {
+            // We must assume the TransformerContext was created using this.newTransformerContextBuilder()
+            DefaultModelProblemCollector problems = (DefaultModelProblemCollector) collector;
+            return new TransformerContext()
+            {
+                @Override
+                public String getUserProperty( String key )
+                {
+                    return context.userProperties.computeIfAbsent( key,
+                                                           k -> request.getUserProperties().getProperty( key ) );
+                }
+
+                @Override
+                public Model getRawModel( String gId, String aId )
+                {
+                    return context.modelByGA.computeIfAbsent( new DefaultTransformerContext.GAKey( gId, aId ),
+                                                              k -> findRawModel( gId, aId ) );
+                }
+
+                @Override
+                public Model getRawModel( Path path )
+                {
+                    return context.modelByPath.computeIfAbsent( path, k -> findRawModel( path ) );
+                }
+
+                private Model findRawModel( String groupId, String artifactId )
+                {
+                    Source source = getSource( groupId, artifactId );
+                    if ( source != null )
+                    {
+                        try
+                        {
+                            ModelBuildingRequest gaBuildingRequest = new FilterModelBuildingRequest( request )
+                            {
+                                @Override
+                                public ModelSource getModelSource()
+                                {
+                                    return (ModelSource) source;
+                                }
+
+                            };
+                            return readRawModel( gaBuildingRequest, problems );
+                        }
+                        catch ( ModelBuildingException e )
+                        {
+                            // gathered with problem collector
+                        }
+                    }
+                    return null;
+                }
+
+                private Model findRawModel( Path p )
+                {
+                    if ( !Files.isRegularFile( p ) )
+                    {
+                        throw new IllegalArgumentException( "Not a regular file: " + p );
+                    }
+
+                    DefaultModelBuildingRequest req = new DefaultModelBuildingRequest( request )
+                                    .setPomFile( p.toFile() )
+                                    .setModelSource( new FileModelSource( p.toFile() ) );
+
+                    try
+                    {
+                        return readRawModel( req, problems );
+                    }
+                    catch ( ModelBuildingException e )
+                    {
+                        // gathered with problem collector
+                    }
+                    return null;
+                }
+            };
+        }
+
+        @Override
+        public TransformerContext build()
+        {
+            return context;
+        }
+
+        public Source getSource( String groupId, String artifactId )
+        {
+            Set<Source> sources = mappedSources.get( new DefaultTransformerContext.GAKey( groupId, artifactId ) );
+            if ( sources == null )
+            {
+                return null;
+            }
+            return sources.stream().reduce( ( a, b ) ->
+            {
+                throw new IllegalStateException( String.format( "No unique Source for %s:%s: %s and %s",
+                                                                groupId, artifactId,
+                                                                a.getLocation(), b.getLocation() ) );
+            } ).orElse( null );
+        }
+
+        public void putSource( String groupId, String artifactId, Source source )
+        {
+            mappedSources.computeIfAbsent( new DefaultTransformerContext.GAKey( groupId, artifactId ),
+                    k -> new HashSet<>() ).add( source );
+        }
+
+    }
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingRequest.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingRequest.java
index 2012bb1..44ad425 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingRequest.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingRequest.java
@@ -40,8 +40,6 @@ public class DefaultModelBuildingRequest
 {
     private Model fileModel;
 
-    private Model rawModel;
-
     private File pomFile;
 
     private ModelSource modelSource;
@@ -74,7 +72,7 @@ public class DefaultModelBuildingRequest
 
     private WorkspaceModelResolver workspaceResolver;
     
-    private TransformerContext context;
+    private TransformerContextBuilder contextBuilder;
 
     /**
      * Creates an empty request.
@@ -104,6 +102,7 @@ public class DefaultModelBuildingRequest
         setModelResolver( request.getModelResolver() );
         setModelBuildingListener( request.getModelBuildingListener() );
         setModelCache( request.getModelCache() );
+        setTransformerContextBuilder( request.getTransformerContextBuilder() );
     }
 
     @Override
@@ -397,17 +396,16 @@ public class DefaultModelBuildingRequest
         this.fileModel = fileModel;
         return this;
     }
-    
+
     @Override
     public Model getRawModel()
     {
-        return rawModel;
+        return null;
     }
 
     @Override
     public ModelBuildingRequest setRawModel( Model rawModel )
     {
-        this.rawModel = rawModel;
         return this;
     }
 
@@ -423,17 +421,18 @@ public class DefaultModelBuildingRequest
         this.workspaceResolver = workspaceResolver;
         return this;
     }
-    
+
     @Override
-    public TransformerContext getTransformerContext()
+    public TransformerContextBuilder getTransformerContextBuilder()
     {
-        return context;
+        return contextBuilder;
     }
     
     @Override
-    public ModelBuildingRequest setTransformerContext( TransformerContext context )
+    public ModelBuildingRequest setTransformerContextBuilder( TransformerContextBuilder contextBuilder )
     {
-        this.context = context;
+        this.contextBuilder = contextBuilder;
         return this;
     }
+
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java
index 13b7714..b2fcfa0 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java
@@ -36,6 +36,7 @@ import org.apache.maven.model.Profile;
 class DefaultModelBuildingResult
     implements ModelBuildingResult
 {
+    private Model fileModel;
 
     private Model effectiveModel;
 
@@ -58,6 +59,35 @@ class DefaultModelBuildingResult
         problems = new ArrayList<>();
     }
 
+    DefaultModelBuildingResult( ModelBuildingResult 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 ) );
+            this.activePomProfiles.put( modelId, result.getActivePomProfiles( modelId ) );
+        }
+    }
+
+    @Override
+    public Model getFileModel()
+    {
+        return fileModel;
+    }
+
+    public DefaultModelBuildingResult setFileModel( Model fileModel )
+    {
+        this.fileModel = fileModel;
+
+        return this;
+    }
+
     @Override
     public Model getEffectiveModel()
     {
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultTransformerContext.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultTransformerContext.java
new file mode 100644
index 0000000..080c62b
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultTransformerContext.java
@@ -0,0 +1,95 @@
+package org.apache.maven.model.building;
+
+/*
+ * 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.
+ */
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.maven.model.Model;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 4.0.0
+ */
+class DefaultTransformerContext implements TransformerContext
+{
+    final Map<String, String> userProperties = new HashMap<>();
+
+    final Map<Path, Model> modelByPath = new HashMap<>();
+
+    final Map<GAKey, Model> modelByGA = new HashMap<>();
+
+    @Override
+    public String getUserProperty( String key )
+    {
+        return userProperties.get( key );
+    }
+
+    @Override
+    public Model getRawModel( Path p )
+    {
+        return modelByPath.get( p );
+    }
+
+    @Override
+    public Model getRawModel( String groupId, String artifactId )
+    {
+        return modelByGA.get( new GAKey( groupId, artifactId ) );
+    }
+
+    static class GAKey
+    {
+        private final String groupId;
+        private final String artifactId;
+        private final int hashCode;
+
+        GAKey( String groupId, String artifactId )
+        {
+            this.groupId = groupId;
+            this.artifactId = artifactId;
+            this.hashCode = Objects.hash( groupId, artifactId );
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return hashCode;
+        }
+
+        @Override
+        public boolean equals( Object obj )
+        {
+            if ( this == obj )
+            {
+                return true;
+            }
+            if ( !( obj instanceof GAKey ) )
+            {
+                return false;
+            }
+
+            GAKey other = (GAKey) obj;
+            return Objects.equals( artifactId, other.artifactId ) && Objects.equals( groupId, other.groupId );
+        }
+    }
+}
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/FilterModelBuildingRequest.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/FilterModelBuildingRequest.java
index 1dd2643..1374cbb 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/FilterModelBuildingRequest.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/FilterModelBuildingRequest.java
@@ -268,7 +268,7 @@ class FilterModelBuildingRequest
         request.setFileModel( fileModel );
         return this;
     }
-    
+
     @Override
     public Model getRawModel()
     {
@@ -294,17 +294,17 @@ class FilterModelBuildingRequest
         request.setWorkspaceModelResolver( workspaceResolver );
         return this;
     }
-    
+
     @Override
-    public TransformerContext getTransformerContext()
+    public TransformerContextBuilder getTransformerContextBuilder()
     {
-        return request.getTransformerContext();
+        return request.getTransformerContextBuilder();
     }
     
     @Override
-    public ModelBuildingRequest setTransformerContext( TransformerContext context )
+    public ModelBuildingRequest setTransformerContextBuilder( TransformerContextBuilder contextBuilder )
     {
-        request.setTransformerContext( context );
+        request.setTransformerContextBuilder( contextBuilder );
         return this;
     }
 }
\ No newline at end of file
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuilder.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuilder.java
index e42469a..74c1fa6 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuilder.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuilder.java
@@ -60,4 +60,6 @@ public interface ModelBuilder
      */
     Result<? extends Model> buildRawModel( File pomFile, int validationLevel, boolean locationTracking );
 
+    TransformerContextBuilder newTransformerContextBuilder();
+
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingRequest.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingRequest.java
index c9451ef..bbd9e8b 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingRequest.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingRequest.java
@@ -63,16 +63,19 @@ public interface ModelBuildingRequest
      * Denotes strict validation as recommended by the current Maven version.
      */
     int VALIDATION_LEVEL_STRICT = VALIDATION_LEVEL_MAVEN_3_0;
-    
+
     /**
-     * 
-     * @return the file model
+     * Gets the file model to build (with profile activation).
+     * If not set, model source will be used to load file model.
+     *
+     * @return The file model to build or {@code null} if not set.
      * @since 4.0.0
      */
     Model getFileModel();
-    
+
     /**
-     * 
+     * Set the file model with profile activation
+     *
      * @param fileModel
      * @return This request, never {@code null}.
      * @since 4.0.0
@@ -80,17 +83,15 @@ public interface ModelBuildingRequest
     ModelBuildingRequest setFileModel( Model fileModel );
 
     /**
-     * Gets the raw model to build. If not set, model source will be used to load raw model.
-     *
-     * @return The raw model to build or {@code null} if not set.
+     * @deprecated rawModel is never set, instead the fileModel is set
      */
+    @Deprecated
     Model getRawModel();
 
     /**
-     * Set raw model.
-     *
-     * @param rawModel
+     * @deprecated setting the rawModel has no effect, instead the fileModel of phase one will be set
      */
+    @Deprecated
     ModelBuildingRequest setRawModel( Model rawModel );
 
     /**
@@ -349,10 +350,9 @@ public interface ModelBuildingRequest
     WorkspaceModelResolver getWorkspaceModelResolver();
 
     ModelBuildingRequest setWorkspaceModelResolver( WorkspaceModelResolver workspaceResolver );
-    
-    TransformerContext getTransformerContext();
 
-    ModelBuildingRequest setTransformerContext( TransformerContext context );
-    
-    
+    TransformerContextBuilder getTransformerContextBuilder();
+
+    ModelBuildingRequest setTransformerContextBuilder( TransformerContextBuilder contextBuilder );
+
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java
index 44b1295..603d214 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java
@@ -43,6 +43,13 @@ public interface ModelBuildingResult
     List<String> getModelIds();
 
     /**
+     *
+     * @return the file model
+     * @since 4.0.0
+     */
+    Model getFileModel();
+
+    /**
      * Gets the assembled model.
      *
      * @return The assembled model, never {@code null}.
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelCacheTag.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelCacheTag.java
index f38bb62..7e0cb0d 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelCacheTag.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelCacheTag.java
@@ -65,7 +65,7 @@ interface ModelCacheTag<T>
     T fromCache( T data );
 
     /**
-     * The tag used to denote raw model data.
+     * The tag used for the raw model without profile activation
      */
     ModelCacheTag<ModelData> RAW = new ModelCacheTag<ModelData>()
     {
@@ -129,12 +129,16 @@ interface ModelCacheTag<T>
 
     };
 
-    ModelCacheTag<Model> FILEMODEL = new ModelCacheTag<Model>() 
+    /**
+     * The tag used for the file model without profile activation
+     * @since 4.0.0
+     */
+    ModelCacheTag<Model> FILE = new ModelCacheTag<Model>()
     {
         @Override
         public String getName()
         {
-            return "file-model";
+            return "file";
         }
 
         @Override
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelData.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelData.java
index 1f39ad4..1ef4341 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelData.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelData.java
@@ -19,10 +19,10 @@ package org.apache.maven.model.building;
  * under the License.
  */
 
-import java.util.List;
+import java.util.Objects;
 
+import org.apache.maven.building.Source;
 import org.apache.maven.model.Model;
-import org.apache.maven.model.Profile;
 
 /**
  * Holds a model along with some auxiliary information. This internal utility class assists the model builder during POM
@@ -32,13 +32,9 @@ import org.apache.maven.model.Profile;
  */
 class ModelData
 {
-    private final ModelSource source;
+    private final Source source;
 
-    private Model model;
-
-    private Model rawModel;
-
-    private List<Profile> activeProfiles;
+    private final Model model;
 
     private String groupId;
 
@@ -51,7 +47,7 @@ class ModelData
      *
      * @param model The model to wrap, may be {@code null}.
      */
-    ModelData( ModelSource source, Model model )
+    ModelData( Source source, Model model )
     {
         this.source = source;
         this.model = model;
@@ -65,16 +61,16 @@ class ModelData
      * @param artifactId The effective artifact identifier of the model, may be {@code null}.
      * @param version The effective version of the model, may be {@code null}.
      */
-    ModelData( ModelSource source, Model model, String groupId, String artifactId, String version )
+    ModelData( Source source, Model model, String groupId, String artifactId, String version )
     {
         this.source = source;
         this.model = model;
-        setGroupId( groupId );
-        setArtifactId( artifactId );
-        setVersion( version );
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.version = version;
     }
 
-    public ModelSource getSource()
+    public Source getSource()
     {
         return source;
     }
@@ -90,56 +86,6 @@ class ModelData
     }
 
     /**
-     * Sets the model being wrapped.
-     *
-     * @param model The model, may be {@code null}.
-     */
-    public void setModel( Model model )
-    {
-        this.model = model;
-    }
-
-    /**
-     * Gets the raw model being wrapped.
-     *
-     * @return The raw model or {@code null} if not set.
-     */
-    public Model getRawModel()
-    {
-        return rawModel;
-    }
-
-    /**
-     * Sets the raw model being wrapped.
-     *
-     * @param rawModel The raw model, may be {@code null}.
-     */
-    public void setRawModel( Model rawModel )
-    {
-        this.rawModel = rawModel;
-    }
-
-    /**
-     * Gets the active profiles from the model.
-     *
-     * @return The active profiles or {@code null} if not set.
-     */
-    public List<Profile> getActiveProfiles()
-    {
-        return activeProfiles;
-    }
-
-    /**
-     * Sets the active profiles from the model.
-     *
-     * @param activeProfiles The active profiles, may be {@code null}.
-     */
-    public void setActiveProfiles( List<Profile> activeProfiles )
-    {
-        this.activeProfiles = activeProfiles;
-    }
-
-    /**
      * Gets the effective group identifier of the model.
      *
      * @return The effective group identifier of the model or an empty string if unknown, never {@code null}.
@@ -150,16 +96,6 @@ class ModelData
     }
 
     /**
-     * Sets the effective group identifier of the model.
-     *
-     * @param groupId The effective group identifier of the model, may be {@code null}.
-     */
-    public void setGroupId( String groupId )
-    {
-        this.groupId = groupId;
-    }
-
-    /**
      * Gets the effective artifact identifier of the model.
      *
      * @return The effective artifact identifier of the model or an empty string if unknown, never {@code null}.
@@ -170,16 +106,6 @@ class ModelData
     }
 
     /**
-     * Sets the effective artifact identifier of the model.
-     *
-     * @param artifactId The effective artifact identifier of the model, may be {@code null}.
-     */
-    public void setArtifactId( String artifactId )
-    {
-        this.artifactId = artifactId;
-    }
-
-    /**
      * Gets the effective version of the model.
      *
      * @return The effective version of the model or an empty string if unknown, never {@code null}.
@@ -190,27 +116,14 @@ class ModelData
     }
 
     /**
-     * Sets the effective version of the model.
-     *
-     * @param version The effective version of the model, may be {@code null}.
-     */
-    public void setVersion( String version )
-    {
-        this.version = version;
-    }
-
-    /**
-     * Gets the effective identifier of the model in the form {@code <groupId>:<artifactId>:<version>}.
+     * Gets unique identifier of the model
      *
      * @return The effective identifier of the model, never {@code null}.
      */
     public String getId()
     {
-        StringBuilder buffer = new StringBuilder( 128 );
-
-        buffer.append( getGroupId() ).append( ':' ).append( getArtifactId() ).append( ':' ).append( getVersion() );
-
-        return buffer.toString();
+        // if source is null, it is the supermodel, which can be accessed via empty string
+        return Objects.toString( source, "" );
     }
 
     @Override
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
index b502e1e..a2556ce 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
@@ -24,12 +24,24 @@ import java.io.InputStream;
 import java.nio.file.Path;
 
 /**
- * 
+ * The ModelSourceTransformer is a way to transform the local pom while streaming the input.
+ *
+ * The {@link #transform(Path, TransformerContext)} method uses a Path on purpose, to ensure the
+ * local pom is the the original source.
+ *
  * @author Robert Scholte
  * @since 4.0.0
  */
 public interface ModelSourceTransformer
 {
+    /**
+     *
+     * @param pomFile the pom file, cannot be null
+     * @param context the context, cannot be null
+     * @return the InputStream for the ModelReader
+     * @throws IOException if an I/O error occurs
+     * @throws TransformerException if the transformation fails
+     */
     InputStream transform( Path pomFile, TransformerContext context )
         throws IOException, TransformerException;
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContext.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContext.java
index 3779a39..d7a43dc 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContext.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContext.java
@@ -47,10 +47,10 @@ public interface TransformerContext
     /**
      * Get the model based on the path, will be used to resolve the parent based on relativePath
      * 
-     * @param p the path
+     * @param pomFile the path to the pomFile
      * @return the model, otherwise {@code null}
      */
-    Model getRawModel( Path p );
+    Model getRawModel( Path pomFile );
     
     /**
      * Get the model from the reactor based on the groupId and artifactId, will be used for reactor dependencies
@@ -60,5 +60,5 @@ public interface TransformerContext
      * @return the model, otherwise {@code null}
      * @throws IllegalStateException if multiple versions of the same GA are part of the reactor
      */
-    Model getRawModel( String groupId, String artifactId ) throws IllegalStateException;
+    Model getRawModel( String groupId, String artifactId );
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContextBuilder.java
similarity index 55%
copy from maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
copy to maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContextBuilder.java
index b502e1e..6117f2a 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContextBuilder.java
@@ -19,17 +19,28 @@ package org.apache.maven.model.building;
  * under the License.
  */
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Path;
-
 /**
- * 
+ * The transformerContextBuilder is responsible for initializing the TransformerContext.
+ * In case rawModels are missing, it could do new buildingRequests on the ModelBuilder.
+ *
  * @author Robert Scholte
  * @since 4.0.0
  */
-public interface ModelSourceTransformer
+public interface TransformerContextBuilder
 {
-    InputStream transform( Path pomFile, TransformerContext context )
-        throws IOException, TransformerException;
+    /**
+     * This method is used to initialize the TransformerContext
+     *
+     * @param request the modelBuildingRequest
+     * @param problems the problemCollector
+     * @return the mutable transformerContext
+     */
+    TransformerContext initialize( ModelBuildingRequest request, ModelProblemCollector problems );
+
+    /**
+     * The immutable transformerContext, can be used after the buildplan is finished.
+     *
+     * @return the immutable transformerContext
+     */
+    TransformerContext build();
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java b/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java
index 1bae747..1d8b264 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java
@@ -59,18 +59,14 @@ public class DefaultModelReader
     {
         this.transformer = transformer;
     }
-    
+
     @Override
     public Model read( File input, Map<String, ?> options )
         throws IOException
     {
         Objects.requireNonNull( input, "input cannot be null" );
 
-        TransformerContext context = null;
-        if ( options != null )
-        {
-            context = (TransformerContext) options.get( "transformerContext" );
-        }        
+        TransformerContext context = getTransformerContext( options );
 
         final InputStream is;
         if ( context == null )
@@ -135,6 +131,12 @@ public class DefaultModelReader
         return (InputSource) value;
     }
 
+    private TransformerContext getTransformerContext( Map<String, ?> options )
+    {
+        Object value = ( options != null ) ? options.get( TRANSFORMER_CONTEXT ) : null;
+        return (TransformerContext) value;
+    }
+
     private Model read( Reader reader, boolean strict, InputSource source )
         throws IOException
     {
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/io/ModelReader.java b/maven-model-builder/src/main/java/org/apache/maven/model/io/ModelReader.java
index 75a5ebe..04b1a30 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/io/ModelReader.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/io/ModelReader.java
@@ -49,6 +49,12 @@ public interface ModelReader
     String INPUT_SOURCE = "org.apache.maven.model.io.inputSource";
 
     /**
+     * The key for the option to provide a transformer context, which can be used to transform the input while reading
+     * to get an advanced version of the model.
+     */
+    String TRANSFORMER_CONTEXT = "transformerContext";
+
+    /**
      * Reads the model from the specified file.
      *
      * @param input The file to deserialize the model from, must not be {@code null}.
diff --git a/maven-model-builder/src/site/apt/index.apt b/maven-model-builder/src/site/apt/index.apt
index e18ad9d..2f20852 100644
--- a/maven-model-builder/src/site/apt/index.apt
+++ b/maven-model-builder/src/site/apt/index.apt
@@ -43,6 +43,22 @@ Maven Model Builder
    Notice that model interpolation hasn't happened yet, then interpolation for file-based activation is limited to
    <<<$\{basedir}>>> (since Maven 3), System properties and request properties
 
+   ** file model validation: <<<ModelValidator>>> ({{{./apidocs/org/apache/maven/model/validation/ModelValidator.html}javadoc}}),
+   with its <<<DefaultModelValidator>>> implementation
+   ({{{./xref/org/apache/maven/model/validation/DefaultModelValidator.html}source}})
+
+   []
+
+ * phase 2, with optional plugin processing
+
+   ** Build up a raw model by re-reading the file and enrich it based on information available in the reactor. Some features:
+
+      *** Resolve version of versionless parents based on realtivePath (including ci-friendly versions)
+
+      *** Resolve version of versionless dependencies that are part of the reactor
+
+   []
+
    ** raw model validation: <<<ModelValidator>>> ({{{./apidocs/org/apache/maven/model/validation/ModelValidator.html}javadoc}}),
    with its <<<DefaultModelValidator>>> implementation
    ({{{./xref/org/apache/maven/model/validation/DefaultModelValidator.html}source}})
@@ -65,10 +81,6 @@ Maven Model Builder
    with its <<<DefaultUrlNormalizer>>> implementation
    ({{{./xref/org/apache/maven/model/path/DefaultUrlNormalizer.html}source}})
 
-   []
-
- * phase 2, with optional plugin processing
-
    ** model path translation: <<<ModelPathTranslator>>> ({{{./apidocs/org/apache/maven/model/path/ModelPathTranslator.html}javadoc}}),
    with its <<<DefaultModelPathTranslator>>> implementation
    ({{{./xref/org/apache/maven/model/path/DefaultModelPathTranslator.html}source}})
@@ -124,7 +136,7 @@ Maven Model Builder
 
   Notice that the 5 URLs from the model (<<<project.url>>>, <<<project.scm.connection>>>, <<<project.scm.developerConnection>>>,
   <<<project.scm.url>>> and <<<project.distributionManagement.site.url>>>) have a special inheritance handling:
-  
+
   ** if not configured in current model, the inherited value is the parent's one with current artifact id appended,
 
   ** since Maven 3.5.0, if <<<project.directory>>> POM property value is defined, it is used instead of artifact id:
@@ -194,6 +206,11 @@ Maven Model Builder
 *----+------+------+
 | <<<settings.*>>> | Local user settings (see {{{../maven-settings/settings.html}settings reference}}) | <<<$\{settings.localRepository\}>>> |
 *----+------+------+
+| <<<changelist>>> \
+<<<revision>>> \
+<<<sha1>>> | CI friendly placeholders for the project version (see {{{/maven-ci-friendly.html}Maven CI Friendly Versions}}) | <<<1.0.0-$\{changelist\}-SNAPSHOT>>> |
+*----+------+------+
+
 
 **  Notice
 
diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
index d2a9e60..252b1d9 100644
--- a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
+++ b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
@@ -69,8 +69,6 @@ public class DefaultModelValidatorTest
 
         SimpleProblemCollector problems = new SimpleProblemCollector( model );
         
-        request.setFileModel( model );
-
         validator.validateEffectiveModel( model, request, problems );
 
         return problems;
@@ -87,8 +85,6 @@ public class DefaultModelValidatorTest
 
         validator.validateFileModel( model, request, problems );
         
-        request.setFileModel( model );
-        
         validator.validateRawModel( model, request, problems );
 
         return problems;
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterFactory.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterFactory.java
index 066be35..2a194d1 100644
--- a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterFactory.java
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterFactory.java
@@ -41,16 +41,19 @@ import org.xml.sax.ext.LexicalHandler;
  */
 public class BuildPomXMLFilterFactory
 {
+    private final boolean consume;
+
     private final Consumer<LexicalHandler> lexicalHandlerConsumer;
-    
-    public BuildPomXMLFilterFactory()
+
+    public BuildPomXMLFilterFactory( Consumer<LexicalHandler> lexicalHandlerConsumer )
     {
-        this( null ); 
+        this( lexicalHandlerConsumer, false );
     }
-    
-    public BuildPomXMLFilterFactory( Consumer<LexicalHandler> lexicalHandlerConsumer )
+
+    public BuildPomXMLFilterFactory( Consumer<LexicalHandler> lexicalHandlerConsumer, boolean consume )
     {
         this.lexicalHandlerConsumer = lexicalHandlerConsumer;
+        this.consume = consume;
     }
 
     /**
@@ -89,6 +92,18 @@ public class BuildPomXMLFilterFactory
             parent = parentFilter;
         }
 
+        CiFriendlyXMLFilter ciFriendlyFilter = new CiFriendlyXMLFilter( consume );
+        getChangelist().ifPresent( ciFriendlyFilter::setChangelist  );
+        getRevision().ifPresent( ciFriendlyFilter::setRevision );
+        getSha1().ifPresent( ciFriendlyFilter::setSha1 );
+
+        if ( ciFriendlyFilter.isSet() )
+        {
+            ciFriendlyFilter.setParent( parent );
+            parent.setLexicalHandler( ciFriendlyFilter );
+            parent = ciFriendlyFilter;
+        }
+
         return new BuildPomXMLFilter( parent );
     }
     
@@ -98,7 +113,7 @@ public class BuildPomXMLFilterFactory
         xmlReader.setFeature( "http://xml.org/sax/features/namespaces", true );
         return xmlReader;
     }
-    
+
     /**
      * @return the mapper or {@code null} if relativePaths don't need to be mapped
      */
@@ -111,4 +126,22 @@ public class BuildPomXMLFilterFactory
     {
         return null;
     }
+
+    // getters for the 3 magic properties of CIFriendly versions ( https://maven.apache.org/maven-ci-friendly.html )
+
+    protected Optional<String> getChangelist()
+    {
+        return Optional.empty();
+    }
+
+    protected Optional<String> getRevision()
+    {
+        return Optional.empty();
+    }
+
+    protected Optional<String> getSha1()
+    {
+        return Optional.empty();
+    }
+
 }
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilter.java
index 0bda110..2da8ddc 100644
--- a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilter.java
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilter.java
@@ -26,27 +26,30 @@ import org.xml.sax.SAXException;
 
 /**
  * Resolves all ci-friendly properties occurrences between version-tags
- * 
+ *
  * @author Robert Scholte
  * @since 4.0.0
  */
 class CiFriendlyXMLFilter
     extends AbstractSAXFilter
 {
+    private final boolean replace;
+
     private Function<String, String> replaceChain = Function.identity();
-    
-    private String characters; 
-    
+
+    private String characters;
+
     private boolean parseVersion;
-    
-    CiFriendlyXMLFilter()
+
+    CiFriendlyXMLFilter( boolean replace )
     {
-        super();
+        this.replace = replace;
     }
 
-    CiFriendlyXMLFilter( AbstractSAXFilter parent )
+    CiFriendlyXMLFilter( AbstractSAXFilter parent, boolean replace )
     {
         super( parent );
+        this.replace = replace;
     }
 
     public CiFriendlyXMLFilter setChangelist( String changelist )
@@ -54,7 +57,7 @@ class CiFriendlyXMLFilter
         replaceChain = replaceChain.andThen( t -> t.replace( "${changelist}", changelist ) );
         return this;
     }
-    
+
     public CiFriendlyXMLFilter setRevision( String revision )
     {
         replaceChain = replaceChain.andThen( t -> t.replace( "${revision}", revision ) );
@@ -66,7 +69,7 @@ class CiFriendlyXMLFilter
         replaceChain = replaceChain.andThen( t -> t.replace( "${sha1}", sha1 ) );
         return this;
     }
-    
+
     /**
      * @return {@code true} is any of the ci properties is set, otherwise {@code false}
      */
@@ -74,7 +77,7 @@ class CiFriendlyXMLFilter
     {
         return !replaceChain.equals( Function.identity() );
     }
-    
+
     @Override
     public void characters( char[] ch, int start, int length )
         throws SAXException
@@ -100,7 +103,7 @@ class CiFriendlyXMLFilter
 
         super.startElement( uri, localName, qName, atts );
     }
-    
+
     @Override
     public void endElement( String uri, String localName, String qName )
         throws SAXException
@@ -108,7 +111,7 @@ class CiFriendlyXMLFilter
         if ( parseVersion )
         {
             // assuming this has the best performance
-            if ( characters != null && characters.contains( "${" ) )
+            if ( replace && characters != null && characters.contains( "${" ) )
             {
                 char[] ch = replaceChain.apply( characters ).toCharArray();
                 super.characters( ch, 0, ch.length );
@@ -124,4 +127,4 @@ class CiFriendlyXMLFilter
 
         super.endElement( uri, localName, qName );
     }
-}
\ No newline at end of file
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterFactory.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterFactory.java
index 18e3208..0f2bd7e 100644
--- a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterFactory.java
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterFactory.java
@@ -20,7 +20,6 @@ package org.apache.maven.xml.sax.filter;
  */
 
 import java.nio.file.Path;
-import java.util.Optional;
 
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.TransformerConfigurationException;
@@ -46,20 +45,10 @@ public class ConsumerPomXMLFilterFactory
     {
         BuildPomXMLFilter parent = buildPomXMLFilterFactory.get( projectPath );
         
-        
+
         // Ensure that xs:any elements aren't touched by next filters
         AbstractSAXFilter filter = new FastForwardFilter( parent );
-        
-        CiFriendlyXMLFilter ciFriendlyFilter = new CiFriendlyXMLFilter( filter );
-        getChangelist().ifPresent( ciFriendlyFilter::setChangelist  );
-        getRevision().ifPresent( ciFriendlyFilter::setRevision );
-        getSha1().ifPresent( ciFriendlyFilter::setSha1 );
-        
-        if ( ciFriendlyFilter.isSet() )
-        {
-            filter = ciFriendlyFilter;
-        }
-        
+
         // Strip modules
         filter = new ModulesXMLFilter( filter );
         // Adjust relativePath
@@ -67,22 +56,4 @@ public class ConsumerPomXMLFilterFactory
         
         return new ConsumerPomXMLFilter( filter );
     }
-    
-    // getters for the 3 magic properties of CIFriendly versions ( https://maven.apache.org/maven-ci-friendly.html )
-
-    protected Optional<String> getChangelist()
-    {
-        return Optional.empty();
-    }
-
-    protected Optional<String> getRevision()
-    {
-        return Optional.empty();
-    }
-
-    protected Optional<String> getSha1()
-    {
-        return Optional.empty();
-    }
-
 }
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ParentXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ParentXMLFilter.java
index ac48188..2d717f0 100644
--- a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ParentXMLFilter.java
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ParentXMLFilter.java
@@ -19,6 +19,7 @@ package org.apache.maven.xml.sax.filter;
  * under the License.
  */
 
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
@@ -34,10 +35,10 @@ import org.xml.sax.SAXException;
  * <p>
  * Transforms relativePath to version.
  * We could decide to simply allow {@code <parent/>}, but let's require the GA for now for checking
- * This filter does NOT remove the relativePath (which is done by {@link RelativePathXMLFilter}, it will only 
- * optionally include the version based on the path 
+ * This filter does NOT remove the relativePath (which is done by {@link RelativePathXMLFilter}, it will only
+ * optionally include the version based on the path
  * </p>
- * 
+ *
  * @author Robert Scholte
  * @since 4.0.0
  */
@@ -51,24 +52,24 @@ class ParentXMLFilter
 
     // whiteSpace after <parent>, to be used to position <version>
     private String parentWhitespace = "";
-    
+
     private String groupId;
 
     private String artifactId;
-    
+
     private String relativePath;
 
     private boolean hasVersion;
-    
+
+    private boolean hasRelativePath;
+
     private Optional<RelativeProject> resolvedParent;
 
     private final Function<Path, Optional<RelativeProject>> relativePathMapper;
-    
+
     private Path projectPath;
 
     /**
-     * 
-     * 
      * @param relativePathMapper
      */
     ParentXMLFilter( Function<Path, Optional<RelativeProject>> relativePathMapper )
@@ -80,7 +81,7 @@ class ParentXMLFilter
     {
         this.projectPath = projectPath;
     }
-    
+
     @Override
     protected boolean isParsing()
     {
@@ -92,7 +93,7 @@ class ParentXMLFilter
     {
         return state;
     }
-    
+
     @Override
     public void startElement( String uri, final String localName, String qName, Attributes atts )
         throws SAXException
@@ -105,10 +106,13 @@ class ParentXMLFilter
         if ( parsingParent )
         {
             state = localName;
-            
+
             hasVersion |= "version".equals( localName );
+
+            // can be set to empty on purpose to enforce repository download
+            hasRelativePath |= "relativePath".equals( localName );
         }
-        
+
         super.startElement( uri, localName, qName, atts );
     }
 
@@ -119,9 +123,9 @@ class ParentXMLFilter
         if ( parsingParent )
         {
             final String eventState = state;
-            
+
             final String charSegment =  new String( ch, start, length );
-            
+
             switch ( eventState )
             {
                 case "parent":
@@ -140,7 +144,7 @@ class ParentXMLFilter
                     break;
             }
         }
-        
+
         super.characters( ch, start, length );
     }
 
@@ -153,33 +157,37 @@ class ParentXMLFilter
             switch ( localName )
             {
                 case "parent":
-                    if ( !hasVersion || relativePath != null )
+                    if ( !hasVersion && ( !hasRelativePath || relativePath != null ) )
                     {
                         resolvedParent =
                             resolveRelativePath( Paths.get( Objects.toString( relativePath, "../pom.xml" ) ) );
                     }
-                    
+                    else
+                    {
+                        resolvedParent = Optional.empty();
+                    }
+
                     if ( !hasVersion && resolvedParent.isPresent() )
                     {
                         try ( Includer i = super.include() )
                         {
                             super.characters( parentWhitespace.toCharArray(), 0,
                                               parentWhitespace.length() );
-                            
+
                             String versionQName = SAXEventUtils.renameQName( qName, "version" );
-                            
+
                             super.startElement( uri, "version", versionQName, null );
-                            
+
                             String resolvedParentVersion = resolvedParent.get().getVersion();
-                            
+
                             super.characters( resolvedParentVersion.toCharArray(), 0,
                                                           resolvedParentVersion.length() );
-                            
+
                             super.endElement( uri, "version", versionQName );
                         }
                     }
                     super.executeEvents();
-                    
+
                     parsingParent = false;
                     break;
                 default:
@@ -187,20 +195,25 @@ class ParentXMLFilter
                     break;
             }
         }
-        
+
         super.endElement( uri, localName, qName );
         state = "";
     }
 
     protected Optional<RelativeProject> resolveRelativePath( Path relativePath )
     {
-        Optional<RelativeProject> mappedProject =
-            relativePathMapper.apply( projectPath.resolve( relativePath ).normalize() );
-        
+        Path pomPath = projectPath.resolve( relativePath );
+        if ( Files.isDirectory( pomPath ) )
+        {
+            pomPath = pomPath.resolve( "pom.xml" );
+        }
+
+        Optional<RelativeProject> mappedProject = relativePathMapper.apply( pomPath.normalize() );
+
         if ( mappedProject.isPresent() )
         {
             RelativeProject project = mappedProject.get();
-            
+
             if ( Objects.equals( groupId, project.getGroupId() )
                 && Objects.equals( artifactId, project.getArtifactId() ) )
             {
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilterTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilterTest.java
index e93c720..7dcdc4f 100644
--- a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilterTest.java
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilterTest.java
@@ -35,7 +35,7 @@ public class CiFriendlyXMLFilterTest extends AbstractXMLFilterTests
     @Before
     public void setUp()
     {
-        filter = new CiFriendlyXMLFilter();
+        filter = new CiFriendlyXMLFilter( true );
         filter.setChangelist( "CHANGELIST" );
     }
     
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterTest.java
index 70dc5b4..5ea73a4 100644
--- a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterTest.java
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterTest.java
@@ -47,7 +47,7 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
     protected AbstractSAXFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
         throws SAXException, ParserConfigurationException, TransformerConfigurationException
     {
-        final BuildPomXMLFilterFactory buildPomXMLFilterFactory = new BuildPomXMLFilterFactory( lexicalHandlerConsumer )
+        final BuildPomXMLFilterFactory buildPomXMLFilterFactory = new BuildPomXMLFilterFactory( lexicalHandlerConsumer, true )
         {
             @Override
             protected Function<Path, Optional<RelativeProject>> getRelativePathMapper()
@@ -60,10 +60,7 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
             {
                 return null;
             }
-        };
-        
-        ConsumerPomXMLFilter filter = new ConsumerPomXMLFilterFactory( buildPomXMLFilterFactory )
-        {
+
             @Override
             protected Optional<String> getSha1()
             {
@@ -81,7 +78,11 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
             {
                 return Optional.of( "CL" );
             }
-        }.get( Paths.get( "pom.xml" ) );
+
+        };
+
+        ConsumerPomXMLFilter filter =
+            new ConsumerPomXMLFilterFactory( buildPomXMLFilterFactory ).get( Paths.get( "pom.xml" ) );
         filter.setFeature( "http://xml.org/sax/features/namespaces", true );
         return filter;
     }
@@ -234,7 +235,7 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
         String actual = transform( input );
         assertThat( actual ).and( expected ).areIdentical();
     }
-    
+
     @Test
     public void lexicalHandler() throws Exception
     {
@@ -245,7 +246,7 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
                         + "<!--post-in-->"
                         + "</modules>"
                         + "<!--after--></project>";
-        String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 
+        String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                         "<project><!--before--><!--after--></project>";
         String actual = transform( input );
         assertThat( actual ).and( expected ).areIdentical();
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ParentXMLFilterTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ParentXMLFilterTest.java
index eee0359..809aef5 100644
--- a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ParentXMLFilterTest.java
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ParentXMLFilterTest.java
@@ -38,15 +38,15 @@ public class ParentXMLFilterTest extends AbstractXMLFilterTests
     protected ParentXMLFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
         throws TransformerException, SAXException, ParserConfigurationException
     {
-        ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.of( new RelativeProject( "GROUPID", 
+        ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.of( new RelativeProject( "GROUPID",
                                                                                            "ARTIFACTID",
                                                                                            "1.0.0" ) ) );
         filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
         lexicalHandlerConsumer.accept( filter );
-        
+
         return filter;
     }
-    
+
     @Test
     public void testMinimum() throws Exception
     {
@@ -89,6 +89,31 @@ public class ParentXMLFilterTest extends AbstractXMLFilterTests
         assertEquals( expected, actual );
     }
 
+    /**
+     * An empty relative path means it must downloaded from a repository.
+     * That implies that the version cannot be solved (if missing, Maven should complain)
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testEmptyRelativePathNoVersion() throws Exception
+    {
+        String input = "<parent>"
+            + "<groupId>GROUPID</groupId>"
+            + "<artifactId>ARTIFACTID</artifactId>"
+            + "<relativePath></relativePath>"
+            + "</parent>";
+        String expected = "<parent>"
+                        + "<groupId>GROUPID</groupId>"
+                        + "<artifactId>ARTIFACTID</artifactId>"
+                        + "<relativePath/>" // SAX optimization, however "" != null ...
+                        + "</parent>";
+
+        String actual = transform( input );
+
+        assertEquals( expected, actual );
+    }
+
     @Test
     public void testNoVersion() throws Exception
     {
@@ -114,7 +139,7 @@ public class ParentXMLFilterTest extends AbstractXMLFilterTests
     {
         ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.ofNullable( null ) );
         filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
-        
+
         String input = "<parent>"
             + "<groupId>GROUPID</groupId>"
             + "<artifactId>ARTIFACTID</artifactId>"
@@ -167,7 +192,7 @@ public class ParentXMLFilterTest extends AbstractXMLFilterTests
 
         assertEquals( expected, actual );
     }
-    
+
     @Test
     public void comment() throws Exception
     {
@@ -189,7 +214,7 @@ public class ParentXMLFilterTest extends AbstractXMLFilterTests
 
         assertEquals( expected, actual );
     }
-    
+
     @Test
     public void testIndent() throws Exception
     {