You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rf...@apache.org on 2020/12/21 21:24:00 UTC

[maven] branch MNG-6957_squashed created (now 12f31c9)

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

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


      at 12f31c9  [MNG-6957] versionless reactor dependencies/parent should work even if modules are aggregated in reverse order

This branch includes the following new commits:

     new 12f31c9  [MNG-6957] versionless reactor dependencies/parent should work even if modules are aggregated in reverse order

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.



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

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

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

commit 12f31c9519b9e558443bfd1ffdc87d8a956607e7
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
---
 .../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  |  39 +-
 .../maven/model/building/DefaultModelBuilder.java  | 572 ++++++++++++++-------
 .../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 |  27 +-
 .../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     |  12 +
 .../maven/model/building/TransformerContext.java   |   6 +-
 ...sformer.java => TransformerContextBuilder.java} |  25 +-
 .../apache/maven/model/io/DefaultModelReader.java  |  12 +-
 .../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  |  13 +-
 .../sax/filter/ConsumerPomXMLFilterFactory.java    |  33 +-
 .../maven/xml/sax/filter/ParentXMLFilter.java      |  21 +-
 .../xml/sax/filter/CiFriendlyXMLFilterTest.java    |   2 +-
 .../xml/sax/filter/ConsumerPomXMLFilterTest.java   |  17 +-
 .../maven/xml/sax/filter/ParentXMLFilterTest.java  |  25 +
 30 files changed, 826 insertions(+), 516 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..a86cb1f 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;
 
@@ -386,36 +381,13 @@ 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();
+        Model model = result.getFileModel().clone();
         
-        poolBuilder.put( model.getPomFile().toPath(),  result.getRawModel() );
+        poolBuilder.put( model.getPomFile().toPath(),  model );
         
-        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 ) );
-        }
-
-        projectIndex.put( result.getModelIds().get( 0 ), project );
-
         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 ) )
@@ -608,6 +571,8 @@ public class DefaultProjectBuilder
                 noErrors = false;
             }
         }
+        
+        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 )
             {
                 //
@@ -1095,16 +1061,22 @@ public class DefaultProjectBuilder
         private final ReactorModelPool modelPool;
 
         private final ReactorModelCache modelCache;
+        
+        private final TransformerContextBuilder transformerContextBuilder;
 
-        InternalConfig( ProjectBuildingRequest request, ReactorModelPool modelPool, ReactorModelCache modelCache )
+        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..122b144 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,7 +32,8 @@ 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
  */
@@ -40,18 +41,20 @@ 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()
     {
@@ -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();
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..f833c25 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;
@@ -257,6 +263,12 @@ public class DefaultModelBuilder
     }
     
     @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 )
         {
-            inputModel = readModel( request.getModelSource(), request.getPomFile(), request, problems );
+            profileInjector.injectProfile( inputModel, activeProfile, request, problems );
         }
 
+        for ( Profile activeProfile : activeExternalProfiles )
+        {
+            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 );
+        
+        result.setEffectiveModel( resultModel );
 
         // 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 );
-        }
-
-        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() );
@@ -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 ( 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();    
+        }
         
-        if ( Features.buildConsumer().isActive() && pomFile != null )
+        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() ) );
+                // must implement TransformContext, but should use request to access system properties/modelcache
+                Model transformedFileModel = modelProcessor.read( pomFile,
+                   Collections.singletonMap( ModelReader.TRANSFORMER_CONTEXT, context ) );
 
-                model.setPomFile( pomFile );
-                
-                // model with locationTrackers, required for proper feedback during validations
-                model = request.getFileModel().clone();
+                // 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( model );
-        String artifactId = model.getArtifactId();
-        String version = getVersion( model );
+        String groupId = getGroupId( rawModel );
+        String artifactId = rawModel.getArtifactId();
+        String version = getVersion( rawModel );
 
-        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 );
-
-        return model;
+        intoCache( request.getModelCache(), modelSource, ModelCacheTag.RAW, modelData );
+ 
+        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 );
@@ -732,12 +803,7 @@ public class DefaultModelBuilder
         }
         else
         {
-            model = fromCache( cache, modelSource, ModelCacheTag.FILEMODEL );
-            
-            if ( model != null )
-            {
-                model = model.clone();
-            }
+            model = fromCache( cache, source, ModelCacheTag.FILE );
         }
         return model;
     }
@@ -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 );
         }
     }
@@ -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,11 +1026,11 @@ 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 )
                 {
@@ -1007,7 +1073,7 @@ public class DefaultModelBuilder
                 }
                 else
                 {
-                    parentData = readParentExternally( childModel, request, problems );
+                    parentData = readParentExternally( childModel, request, result, problems );
                     
                     intoCache( request.getModelCache(), 
                               parentData.getGroupId(), parentData.getArtifactId(),
@@ -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();
@@ -1048,8 +1114,17 @@ public class DefaultModelBuilder
             {
                 return null;
             }
+            
+            ModelBuildingRequest candidateBuildRequest = new FilterModelBuildingRequest( request ) 
+            {
+                  @Override
+                public ModelSource getModelSource()
+                {
+                    return candidateSource;
+                }  
+            };
 
-            candidateModel = readModel( candidateSource, null, request, problems );
+            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 )
         {
@@ -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..731c583 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;
 
@@ -57,6 +58,35 @@ class DefaultModelBuildingResult
         activeExternalProfiles = new ArrayList<>();
         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..1930a48
--- /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..85455f6 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
@@ -59,5 +59,7 @@ public interface ModelBuilder
      * Performs only the part of {@link ModelBuilder#build(ModelBuildingRequest)} that loads the raw model
      */
     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..5bd1ddf 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 );
 
     /**
@@ -350,9 +351,9 @@ public interface ModelBuildingRequest
 
     ModelBuildingRequest setWorkspaceModelResolver( WorkspaceModelResolver workspaceResolver );
     
-    TransformerContext getTransformerContext();
+    TransformerContextBuilder getTransformerContextBuilder();
 
-    ModelBuildingRequest setTransformerContext( TransformerContext context );
+    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..f9615c8 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
@@ -41,6 +41,13 @@ public interface ModelBuildingResult
      * @return The model identifiers from the lineage of models, never {@code null}.
      */
     List<String> getModelIds();
+    
+    /**
+     * 
+     * @return the file model
+     * @since 4.0.0
+     */
+    Model getFileModel();
 
     /**
      * Gets the assembled model.
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..c6fdc1c 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..bc3743b 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..a10798e 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..23cab35 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
@@ -66,11 +66,7 @@ public class DefaultModelReader
     {
         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 )
@@ -134,6 +130,12 @@ public class DefaultModelReader
         Object value = ( options != null ) ? options.get( INPUT_SOURCE ) : null;
         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..dd6e1e5 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
@@ -47,6 +47,12 @@ public interface ModelReader
      * location tracking.
      */
     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.
diff --git a/maven-model-builder/src/site/apt/index.apt b/maven-model-builder/src/site/apt/index.apt
index e18ad9d..3b5355a 100644
--- a/maven-model-builder/src/site/apt/index.apt
+++ b/maven-model-builder/src/site/apt/index.apt
@@ -38,11 +38,27 @@ Maven Model Builder
  The sequence is divided into 2 phases:
 
  * phase 1
-
+ 
    ** profile activation: see {{{./apidocs/org/apache/maven/model/profile/activation/package-summary.html}available activators}}.
    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}})
@@ -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..ef74880 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
@@ -33,20 +33,23 @@ import org.xml.sax.SAXException;
 class CiFriendlyXMLFilter
     extends AbstractSAXFilter
 {
+    private final boolean replace;
+    
     private Function<String, String> replaceChain = Function.identity();
     
     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 )
@@ -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 );
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..220e062 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;
 
@@ -60,6 +61,8 @@ class ParentXMLFilter
 
     private boolean hasVersion;
     
+    private boolean hasRelativePath;
+    
     private Optional<RelativeProject> resolvedParent;
 
     private final Function<Path, Optional<RelativeProject>> relativePathMapper;
@@ -107,6 +110,9 @@ class ParentXMLFilter
             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 );
@@ -153,11 +159,15 @@ 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() )
                     {
@@ -194,8 +204,13 @@ class ParentXMLFilter
 
     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() )
         {
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..5cbf707 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
@@ -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
     {