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/06/22 19:25:03 UTC
[maven] branch master updated: [MNG-6656] Introduce base for
build/consumer pom
This is an automated email from the ASF dual-hosted git repository.
rfscholte pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new bdec668 [MNG-6656] Introduce base for build/consumer pom
bdec668 is described below
commit bdec668de9c600165bb69c95b6ea0625d9f74fb0
Author: rfscholte <rf...@apache.org>
AuthorDate: Mon Jun 22 21:24:49 2020 +0200
[MNG-6656] Introduce base for build/consumer pom
---
maven-core/pom.xml | 22 ++
.../aether/ConsumerModelSourceTransformer.java | 110 ++++++++
.../DefaultRepositorySystemSessionFactory.java | 56 +++-
.../maven/project/DefaultProjectBuilder.java | 77 ++++--
.../apache/maven/project/ProjectModelResolver.java | 46 ++--
.../org/apache/maven/project/ReactorModelPool.java | 120 +++++++--
.../DefaultConsumerPomXMLFilterFactory.java | 65 +++++
.../apache/maven/project/ProjectBuilderTest.java | 2 +-
.../maven/repository/TestRepositorySystem.java | 4 +-
maven-model-builder/pom.xml | 6 +-
.../java/org/apache/maven/feature/Features.java | 63 +++++
.../building/AbstractModelSourceTransformer.java | 199 ++++++++++++++
.../building/BuildModelSourceTransformer.java | 84 ++++++
.../building/DefaultBuildPomXMLFilterFactory.java | 92 +++++++
.../maven/model/building/DefaultModelBuilder.java | 275 +++++++++++++++++---
.../model/building/DefaultModelBuilderFactory.java | 9 +-
.../building/DefaultModelBuildingRequest.java | 30 ++-
.../building/DefaultModelSourceTransformer.java | 43 +++
.../model/building/FilterModelBuildingRequest.java | 27 +-
.../maven/model/building/ModelBuildingRequest.java | 20 ++
.../apache/maven/model/building/ModelProblem.java | 3 +-
.../model/building/ModelSourceTransformer.java | 35 +++
.../maven/model/building/TransformerContext.java | 64 +++++
.../maven/model/building/TransformerException.java | 40 +++
.../apache/maven/model/io/DefaultModelReader.java | 43 ++-
.../model/validation/DefaultModelValidator.java | 39 +--
.../maven/model/validation/ModelValidator.java | 16 +-
.../model/building/FileToRawModelMergerTest.java | 82 ++++++
.../DefaultInheritanceAssemblerTest.java | 21 +-
.../validation/DefaultModelValidatorTest.java | 21 +-
maven-xml/pom.xml | 47 ++++
.../main/java/org/apache/maven/xml/Factories.java | 118 +++++++++
.../java/org/apache/maven/xml/sax/SAXEvent.java | 34 +++
.../org/apache/maven/xml/sax/SAXEventFactory.java | 144 ++++++++++
.../org/apache/maven/xml/sax/SAXEventUtils.java | 49 ++++
.../maven/xml/sax/ext/CommentRenormalizer.java | 108 ++++++++
.../xml/sax/filter/AbstractEventXMLFilter.java | 289 +++++++++++++++++++++
.../maven/xml/sax/filter/AbstractSAXFilter.java | 130 +++++++++
.../maven/xml/sax/filter/BuildPomXMLFilter.java | 58 +++++
.../xml/sax/filter/BuildPomXMLFilterFactory.java | 112 ++++++++
.../xml/sax/filter/BuildPomXMLFilterListener.java | 42 +++
.../maven/xml/sax/filter/CiFriendlyXMLFilter.java | 83 ++++++
.../maven/xml/sax/filter/ConsumerPomXMLFilter.java | 54 ++++
.../sax/filter/ConsumerPomXMLFilterFactory.java | 89 +++++++
.../apache/maven/xml/sax/filter/DependencyKey.java | 92 +++++++
.../maven/xml/sax/filter/FastForwardFilter.java | 128 +++++++++
.../maven/xml/sax/filter/ModulesXMLFilter.java | 111 ++++++++
.../maven/xml/sax/filter/ParentXMLFilter.java | 210 +++++++++++++++
.../xml/sax/filter/ReactorDependencyXMLFilter.java | 165 ++++++++++++
.../xml/sax/filter/RelativePathXMLFilter.java | 108 ++++++++
.../maven/xml/sax/filter/RelativeProject.java | 56 ++++
.../apache/maven/xml/sax/SAXEventUtilsTest.java | 43 +++
.../maven/xml/sax/ext/CommentRenormalizerTest.java | 84 ++++++
.../xml/sax/filter/AbstractXMLFilterTests.java | 119 +++++++++
.../xml/sax/filter/ConsumerPomXMLFilterTest.java | 235 +++++++++++++++++
.../maven/xml/sax/filter/ModulesXMLFilterTest.java | 95 +++++++
.../maven/xml/sax/filter/ParentXMLFilterTest.java | 215 +++++++++++++++
.../sax/filter/ReactorDependencyXMLFilterTest.java | 145 +++++++++++
.../xml/sax/filter/RelativePathXMLFilterTest.java | 115 ++++++++
pom.xml | 12 +
60 files changed, 4849 insertions(+), 155 deletions(-)
diff --git a/maven-core/pom.xml b/maven-core/pom.xml
index 43a6bee..7128c35 100644
--- a/maven-core/pom.xml
+++ b/maven-core/pom.xml
@@ -62,6 +62,10 @@ under the License.
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
+ <artifactId>maven-xml</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
</dependency>
<dependency>
@@ -142,6 +146,11 @@ under the License.
<artifactId>hamcrest-library</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-assertj</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -218,6 +227,19 @@ under the License.
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <!-- <phase></phase> -->
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</project>
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
new file mode 100644
index 0000000..728c78e
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java
@@ -0,0 +1,110 @@
+package org.apache.maven.internal.aether;
+
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.apache.maven.model.building.AbstractModelSourceTransformer;
+import org.apache.maven.model.building.DefaultBuildPomXMLFilterFactory;
+import org.apache.maven.model.building.TransformerContext;
+import org.apache.maven.xml.Factories;
+import org.apache.maven.xml.internal.DefaultConsumerPomXMLFilterFactory;
+import org.apache.maven.xml.sax.filter.AbstractSAXFilter;
+import org.xml.sax.SAXException;
+
+class ConsumerModelSourceTransformer extends AbstractModelSourceTransformer
+{
+ @Override
+ protected AbstractSAXFilter getSAXFilter( Path pomFile, TransformerContext context )
+ throws TransformerConfigurationException, SAXException, ParserConfigurationException
+ {
+ return new DefaultConsumerPomXMLFilterFactory( new DefaultBuildPomXMLFilterFactory( context ) ).get( pomFile );
+ }
+
+ /**
+ * This transformer will ensure that encoding and version are kept.
+ * However, it cannot prevent:
+ * <ul>
+ * <li>attributes will be on one line</li>
+ * <li>Unnecessary whitespace before the rootelement will be removed</li>
+ * </ul>
+ */
+ @Override
+ protected TransformerHandler getTransformerHandler( Path pomFile )
+ throws IOException, org.apache.maven.model.building.TransformerException
+ {
+ final TransformerHandler transformerHandler;
+
+ final SAXTransformerFactory transformerFactory =
+ (SAXTransformerFactory) Factories.newTransformerFactory();
+
+ // Keep same encoding+version
+ try ( InputStream input = Files.newInputStream( pomFile ) )
+ {
+ XMLStreamReader streamReader =
+ XMLInputFactory.newFactory().createXMLStreamReader( input );
+
+ transformerHandler = transformerFactory.newTransformerHandler();
+
+ final String encoding = streamReader.getCharacterEncodingScheme();
+ final String version = streamReader.getVersion();
+
+ Transformer transformer = transformerHandler.getTransformer();
+ transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
+ if ( encoding == null && version == null )
+ {
+ transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
+ }
+ else
+ {
+ transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "no" );
+
+ if ( encoding != null )
+ {
+ transformer.setOutputProperty( OutputKeys.ENCODING, encoding );
+ }
+ if ( version != null )
+ {
+ transformer.setOutputProperty( OutputKeys.VERSION, version );
+ }
+ }
+ }
+ catch ( XMLStreamException | TransformerConfigurationException e )
+ {
+ throw new org.apache.maven.model.building.TransformerException(
+ "Failed to detect XML encoding and version", e );
+ }
+ return transformerHandler;
+ }
+
+}
diff --git a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
index 248a3b6..28f75cd 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
@@ -24,6 +24,9 @@ import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.bridge.MavenRepositorySystem;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.feature.Features;
+import org.apache.maven.model.building.TransformerContext;
+import org.apache.maven.model.building.TransformerException;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Proxy;
@@ -38,12 +41,16 @@ import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.SessionData;
+import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.resolution.ResolutionErrorPolicy;
import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
+import org.eclipse.aether.transform.FileTransformer;
+import org.eclipse.aether.transform.TransformException;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
@@ -53,8 +60,13 @@ import org.eclipse.sisu.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
+
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@@ -96,7 +108,6 @@ public class DefaultRepositorySystemSessionFactory
public DefaultRepositorySystemSession newRepositorySession( MavenExecutionRequest request )
{
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
-
session.setCache( request.getRepositoryCache() );
Map<Object, Object> configProps = new LinkedHashMap<>();
@@ -139,7 +150,6 @@ public class DefaultRepositorySystemSessionFactory
session.setLocalRepositoryManager( simpleLocalRepoMgrFactory.newInstance( session, localRepo ) );
logger.info( "Disabling enhanced local repository: using legacy is strongly discouraged to ensure"
+ " build reproducibility." );
-
}
catch ( NoLocalRepositoryManagerException e )
{
@@ -238,6 +248,11 @@ public class DefaultRepositorySystemSessionFactory
mavenRepositorySystem.injectProxy( session, request.getPluginArtifactRepositories() );
mavenRepositorySystem.injectAuthentication( session, request.getPluginArtifactRepositories() );
+ if ( Features.buildConsumer().isActive() )
+ {
+ session.setFileTransformerManager( a -> getTransformersForArtifact( a, session.getData() ) );
+ }
+
return session;
}
@@ -266,5 +281,40 @@ public class DefaultRepositorySystemSessionFactory
return props.getProperty( "version", "unknown-version" );
}
+
+ private Collection<FileTransformer> getTransformersForArtifact( final Artifact artifact,
+ final SessionData sessionData )
+ {
+ TransformerContext context = (TransformerContext) sessionData.get( TransformerContext.KEY );
+ Collection<FileTransformer> transformers = new ArrayList<>();
+
+ // In case of install:install-file there's no transformer context, as the goal is unrelated to the lifecycle.
+ if ( "pom".equals( artifact.getExtension() ) && context != null )
+ {
+ transformers.add( new FileTransformer()
+ {
+ @Override
+ public InputStream transformData( File pomFile )
+ throws IOException, TransformException
+ {
+ try
+ {
+ return new ConsumerModelSourceTransformer().transform( pomFile.toPath(), context );
+ }
+ catch ( TransformerException e )
+ {
+ throw new TransformException( e );
+ }
+ }
+
+ @Override
+ public Artifact transformArtifact( Artifact artifact )
+ {
+ return artifact;
+ }
+ } );
+ }
+ return Collections.unmodifiableCollection( transformers );
+ }
-}
+}
\ No newline at end of file
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 1ebc1fc..624f6ad 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,6 +21,7 @@ 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;
@@ -44,6 +45,7 @@ import org.apache.maven.artifact.InvalidRepositoryException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.LegacyLocalRepositoryManager;
import org.apache.maven.bridge.MavenRepositorySystem;
+import org.apache.maven.feature.Features;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
@@ -53,6 +55,7 @@ import org.apache.maven.model.Model;
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.DefaultModelBuildingRequest;
import org.apache.maven.model.building.DefaultModelProblem;
import org.apache.maven.model.building.FileModelSource;
@@ -64,6 +67,7 @@ import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.model.building.ModelSource;
import org.apache.maven.model.building.StringModelSource;
+import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.resolution.ModelResolver;
import org.apache.maven.repository.internal.ArtifactDescriptorUtils;
import org.codehaus.plexus.logging.Logger;
@@ -291,6 +295,7 @@ public class DefaultProjectBuilder
request.setBuildStartTime( configuration.getBuildStartTime() );
request.setModelResolver( resolver );
request.setModelCache( config.modelCache );
+ request.setTransformerContext( (TransformerContext) config.session.getData().get( TransformerContext.KEY ) );
return request;
}
@@ -342,7 +347,16 @@ public class DefaultProjectBuilder
artifact.setResolved( true );
}
- return build( localProject ? pomFile : null, new FileModelSource( pomFile ), config );
+ if ( localProject )
+ {
+ return build( pomFile, new FileModelSource( pomFile ), config );
+ }
+ else
+ {
+ return build( null, new ArtifactModelSource( pomFile, artifact.getGroupId(), artifact.getArtifactId(),
+ artifact.getVersion() ),
+ config );
+ }
}
private ModelSource createStubModelSource( Artifact artifact )
@@ -369,7 +383,33 @@ public class DefaultProjectBuilder
List<InterimResult> interimResults = new ArrayList<>();
- ReactorModelPool modelPool = new ReactorModelPool();
+ 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() );
@@ -378,9 +418,7 @@ public class DefaultProjectBuilder
boolean noErrors =
build( results, interimResults, projectIndex, pomFiles, new LinkedHashSet<>(), true, recursive,
- config );
-
- populateReactorModelPool( modelPool, interimResults );
+ config, poolBuilder );
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
@@ -406,7 +444,8 @@ 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,
- boolean isRoot, boolean recursive, InternalConfig config )
+ boolean root, boolean recursive, InternalConfig config,
+ ReactorModelPool.Builder poolBuilder )
{
boolean noErrors = true;
@@ -414,7 +453,8 @@ public class DefaultProjectBuilder
{
aggregatorFiles.add( pomFile );
- if ( !build( results, interimResults, projectIndex, pomFile, aggregatorFiles, isRoot, recursive, config ) )
+ if ( !build( results, interimResults, projectIndex, pomFile, aggregatorFiles, root, recursive, config,
+ poolBuilder ) )
{
noErrors = false;
}
@@ -428,7 +468,8 @@ public class DefaultProjectBuilder
@SuppressWarnings( "checkstyle:parameternumber" )
private boolean build( List<ProjectBuildingResult> results, List<InterimResult> interimResults,
Map<String, MavenProject> projectIndex, File pomFile, Set<File> aggregatorFiles,
- boolean isRoot, boolean recursive, InternalConfig config )
+ boolean isRoot, boolean recursive, InternalConfig config,
+ ReactorModelPool.Builder poolBuilder )
{
boolean noErrors = true;
@@ -465,6 +506,9 @@ public class DefaultProjectBuilder
}
Model model = result.getEffectiveModel();
+
+ poolBuilder.put( model.getPomFile().toPath(), result.getRawModel() );
+
try
{
// first pass: build without building parent.
@@ -559,7 +603,7 @@ public class DefaultProjectBuilder
interimResult.modules = new ArrayList<>();
if ( !build( results, interimResult.modules, projectIndex, moduleFiles, aggregatorFiles, false,
- recursive, config ) )
+ recursive, config, poolBuilder ) )
{
noErrors = false;
}
@@ -595,17 +639,6 @@ public class DefaultProjectBuilder
}
- private void populateReactorModelPool( ReactorModelPool reactorModelPool, List<InterimResult> interimResults )
- {
- for ( InterimResult interimResult : interimResults )
- {
- Model model = interimResult.result.getEffectiveModel();
- reactorModelPool.put( model.getGroupId(), model.getArtifactId(), model.getVersion(), model.getPomFile() );
-
- populateReactorModelPool( reactorModelPool, interimResult.modules );
- }
- }
-
private boolean build( List<ProjectBuildingResult> results, List<MavenProject> projects,
Map<String, MavenProject> projectIndex, List<InterimResult> interimResults,
ProjectBuildingRequest request, Map<File, Boolean> profilesXmls,
@@ -865,7 +898,7 @@ public class DefaultProjectBuilder
DeploymentRepository r = project.getDistributionManagement().getRepository();
if ( !StringUtils.isEmpty( r.getId() ) && !StringUtils.isEmpty( r.getUrl() ) )
{
- ArtifactRepository repo = repositorySystem.buildArtifactRepository( r );
+ ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository( r );
repositorySystem.injectProxy( projectBuildingRequest.getRepositorySession(),
Arrays.asList( repo ) );
repositorySystem.injectAuthentication( projectBuildingRequest.getRepositorySession(),
@@ -889,7 +922,7 @@ public class DefaultProjectBuilder
DeploymentRepository r = project.getDistributionManagement().getSnapshotRepository();
if ( !StringUtils.isEmpty( r.getId() ) && !StringUtils.isEmpty( r.getUrl() ) )
{
- ArtifactRepository repo = repositorySystem.buildArtifactRepository( r );
+ ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository( r );
repositorySystem.injectProxy( projectBuildingRequest.getRepositorySession(),
Arrays.asList( repo ) );
repositorySystem.injectAuthentication( projectBuildingRequest.getRepositorySession(),
diff --git a/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java b/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java
index 24b36dd..bcc3730 100644
--- a/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java
+++ b/maven-core/src/main/java/org/apache/maven/project/ProjectModelResolver.java
@@ -19,7 +19,6 @@ package org.apache.maven.project;
* under the License.
*/
-import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -28,8 +27,10 @@ import java.util.List;
import java.util.Set;
import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.apache.maven.model.Repository;
+import org.apache.maven.model.building.ArtifactModelSource;
import org.apache.maven.model.building.FileModelSource;
import org.apache.maven.model.building.ModelSource;
import org.apache.maven.model.resolution.InvalidRepositoryException;
@@ -155,10 +156,10 @@ public class ProjectModelResolver
private static void removeMatchingRepository( Iterable<RemoteRepository> repositories, final String id )
{
- Iterator iterator = repositories.iterator( );
+ Iterator<RemoteRepository> iterator = repositories.iterator( );
while ( iterator.hasNext() )
{
- RemoteRepository next = ( RemoteRepository ) iterator.next();
+ RemoteRepository next = iterator.next();
if ( next.getId().equals( id ) )
{
iterator.remove();
@@ -174,32 +175,20 @@ public class ProjectModelResolver
public ModelSource resolveModel( String groupId, String artifactId, String version )
throws UnresolvableModelException
{
- File pomFile = null;
+ Artifact pomArtifact = new DefaultArtifact( groupId, artifactId, "", "pom", version );
- if ( modelPool != null )
+ try
{
- pomFile = modelPool.get( groupId, artifactId, version );
+ ArtifactRequest request = new ArtifactRequest( pomArtifact, repositories, context );
+ request.setTrace( trace );
+ pomArtifact = resolver.resolveArtifact( session, request ).getArtifact();
}
-
- if ( pomFile == null )
+ catch ( ArtifactResolutionException e )
{
- Artifact pomArtifact = new DefaultArtifact( groupId, artifactId, "", "pom", version );
-
- try
- {
- ArtifactRequest request = new ArtifactRequest( pomArtifact, repositories, context );
- request.setTrace( trace );
- pomArtifact = resolver.resolveArtifact( session, request ).getArtifact();
- }
- catch ( ArtifactResolutionException e )
- {
- throw new UnresolvableModelException( e.getMessage(), groupId, artifactId, version, e );
- }
-
- pomFile = pomArtifact.getFile();
+ throw new UnresolvableModelException( e.getMessage(), groupId, artifactId, version, e );
}
- return new FileModelSource( pomFile );
+ return new ArtifactModelSource( pomArtifact.getFile(), groupId, artifactId, version );
}
@Override
@@ -285,6 +274,17 @@ public class ProjectModelResolver
}
dependency.setVersion( versionRangeResult.getHighestVersion().toString() );
+
+ if ( modelPool != null )
+ {
+ Model model =
+ modelPool.get( dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() );
+
+ if ( model != null )
+ {
+ return new FileModelSource( model.getPomFile() );
+ }
+ }
return resolveModel( dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() );
}
diff --git a/maven-core/src/main/java/org/apache/maven/project/ReactorModelPool.java b/maven-core/src/main/java/org/apache/maven/project/ReactorModelPool.java
index 64b30dd..b96b14b 100644
--- a/maven-core/src/main/java/org/apache/maven/project/ReactorModelPool.java
+++ b/maven-core/src/main/java/org/apache/maven/project/ReactorModelPool.java
@@ -19,53 +19,124 @@ package org.apache.maven.project;
* under the License.
*/
-import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.maven.model.Model;
/**
- * Holds all POM files that are known to the reactor. This allows the project builder to resolve imported POMs from the
+ * Holds all Models that are known to the reactor. This allows the project builder to resolve imported Models from the
* reactor when building another project's effective model.
*
* @author Benjamin Bentmann
+ * @author Robert Scholte
*/
class ReactorModelPool
{
+ private final Map<GAKey, Set<Model>> modelsByGa = new HashMap<>();
+
+ private final Map<Path, Model> modelsByPath = new HashMap<>();
+
+ /**
+ * Get the model by its GAV or (since 3.7.0) by its GA if there is only one.
+ *
+ * @param groupId, never {@code null}
+ * @param artifactId, never {@code null}
+ * @param version, can be {@code null}
+ * @return the matching model or {@code null}
+ * @throws IllegalStateException if version was null and multiple modules share the same groupId + artifactId
+ */
+ public Model get( String groupId, String artifactId, String version )
+ {
+ return modelsByGa.getOrDefault( new GAKey( groupId, artifactId ), Collections.emptySet() ).stream()
+ .filter( m -> version == null || version.equals( getVersion( m ) ) )
+ .reduce( ( a, b ) ->
+ {
+ throw new IllegalStateException( "Multiple modules with key "
+ + a.getGroupId() + ':' + a.getArtifactId() );
+ } ).orElse( null );
+ }
- private final Map<CacheKey, File> pomFiles = new HashMap<>();
-
- public File get( String groupId, String artifactId, String version )
+ /**
+ * Find model by path, useful when location the parent by relativePath
+ *
+ * @param path
+ * @return the matching model or {@code null}
+ * @since 3.7.0
+ */
+ public Model get( Path path )
{
- return pomFiles.get( new CacheKey( groupId, artifactId, version ) );
+ final Path pomFile;
+ if ( Files.isDirectory( path ) )
+ {
+ pomFile = path.resolve( "pom.xml" );
+ }
+ else
+ {
+ pomFile = path;
+ }
+ return modelsByPath.get( pomFile );
+ }
+
+ private String getVersion( Model model )
+ {
+ String version = model.getVersion();
+ if ( version == null && model.getParent() != null )
+ {
+ version = model.getParent().getVersion();
+ }
+ return version;
}
- public void put( String groupId, String artifactId, String version, File pomFile )
+ static class Builder
{
- pomFiles.put( new CacheKey( groupId, artifactId, version ), pomFile );
+ private ReactorModelPool pool = new ReactorModelPool();
+
+ Builder put( Path pomFile, Model model )
+ {
+ pool.modelsByPath.put( pomFile, model );
+ pool.modelsByGa.computeIfAbsent( new GAKey( getGroupId( model ), model.getArtifactId() ),
+ k -> new HashSet<Model>() ).add( model );
+ return this;
+ }
+
+ ReactorModelPool build()
+ {
+ return pool;
+ }
+
+ private static String getGroupId( Model model )
+ {
+ String groupId = model.getGroupId();
+ if ( groupId == null && model.getParent() != null )
+ {
+ groupId = model.getParent().getGroupId();
+ }
+ return groupId;
+ }
}
- private static final class CacheKey
+ private static final class GAKey
{
private final String groupId;
private final String artifactId;
- private final String version;
-
private final int hashCode;
- CacheKey( String groupId, String artifactId, String version )
+ GAKey( String groupId, String artifactId )
{
this.groupId = ( groupId != null ) ? groupId : "";
this.artifactId = ( artifactId != null ) ? artifactId : "";
- this.version = ( version != null ) ? version : "";
- int hash = 17;
- hash = hash * 31 + this.groupId.hashCode();
- hash = hash * 31 + this.artifactId.hashCode();
- hash = hash * 31 + this.version.hashCode();
- hashCode = hash;
+ hashCode = Objects.hash( this.groupId, this.artifactId );
}
@Override
@@ -76,15 +147,9 @@ class ReactorModelPool
return true;
}
- if ( !( obj instanceof CacheKey ) )
- {
- return false;
- }
-
- CacheKey that = (CacheKey) obj;
+ GAKey that = (GAKey) obj;
- return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId )
- && version.equals( that.version );
+ return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId );
}
@Override
@@ -97,10 +162,9 @@ class ReactorModelPool
public String toString()
{
StringBuilder buffer = new StringBuilder( 128 );
- buffer.append( groupId ).append( ':' ).append( artifactId ).append( ':' ).append( version );
+ buffer.append( groupId ).append( ':' ).append( artifactId );
return buffer.toString();
}
-
}
}
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
new file mode 100644
index 0000000..f49e62d
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/xml/internal/DefaultConsumerPomXMLFilterFactory.java
@@ -0,0 +1,65 @@
+package org.apache.maven.xml.internal;
+
+/*
+ * 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.util.Optional;
+
+import org.apache.maven.model.building.DefaultBuildPomXMLFilterFactory;
+import org.apache.maven.model.building.TransformerContext;
+import org.apache.maven.xml.sax.filter.ConsumerPomXMLFilterFactory;
+
+/**
+ * The default implementation of the {@link ConsumerPomXMLFilterFactory}
+ * It will provide several values for the consumer pom based on its context.
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+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-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java b/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
index da43088..538f887 100644
--- a/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
+++ b/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
@@ -159,7 +159,7 @@ public class ProjectBuilderTest
File parent = new File( tempDir.toFile(), "pom.xml" );
String parentContent = FileUtils.fileRead( parent );
parentContent = parentContent.replaceAll( "<packaging>pom</packaging>",
- "<packaging>pom</packaging><properties><addedProperty>addedValue</addedProperty></properties>" );
+ "<packaging>pom</packaging><properties><addedProperty>addedValue</addedProperty></properties>" );
FileUtils.fileWrite( parent, "UTF-8", parentContent );
// re-build pom with modified parent
ProjectBuildingResult result = projectBuilder.build( child, configuration );
diff --git a/maven-core/src/test/java/org/apache/maven/repository/TestRepositorySystem.java b/maven-core/src/test/java/org/apache/maven/repository/TestRepositorySystem.java
index a3c1c0d..c95f428 100644
--- a/maven-core/src/test/java/org/apache/maven/repository/TestRepositorySystem.java
+++ b/maven-core/src/test/java/org/apache/maven/repository/TestRepositorySystem.java
@@ -111,14 +111,14 @@ public class TestRepositorySystem
public ArtifactRepository createDefaultLocalRepository()
throws InvalidRepositoryException
{
- return createLocalRepository( new File( System.getProperty( "basedir", "" ), "target/local-repo" ).getAbsoluteFile() );
+ return createLocalRepository( new File( System.getProperty( "basedir", "." ), "target/local-repo" ).getAbsoluteFile() );
}
public ArtifactRepository createDefaultRemoteRepository()
throws InvalidRepositoryException
{
return new MavenArtifactRepository( DEFAULT_REMOTE_REPO_ID, "file://"
- + new File( System.getProperty( "basedir", "" ), "src/test/remote-repo" ).toURI().getPath(),
+ + new File( System.getProperty( "basedir", "." ), "src/test/remote-repo" ).getAbsoluteFile().toURI().getPath(),
new DefaultRepositoryLayout(), new ArtifactRepositoryPolicy(),
new ArtifactRepositoryPolicy() );
}
diff --git a/maven-model-builder/pom.xml b/maven-model-builder/pom.xml
index 81d2587..583d6cb 100644
--- a/maven-model-builder/pom.xml
+++ b/maven-model-builder/pom.xml
@@ -59,6 +59,11 @@ under the License.
<artifactId>maven-builder-support</artifactId>
</dependency>
<dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-xml</artifactId>
+ </dependency>
+ <!-- Testing -->
+ <dependency>
<groupId>org.eclipse.sisu</groupId>
<artifactId>org.eclipse.sisu.inject</artifactId>
</dependency>
@@ -67,7 +72,6 @@ under the License.
<artifactId>org.eclipse.sisu.plexus</artifactId>
<scope>test</scope>
</dependency>
- <!-- Testing -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
diff --git a/maven-model-builder/src/main/java/org/apache/maven/feature/Features.java b/maven-model-builder/src/main/java/org/apache/maven/feature/Features.java
new file mode 100644
index 0000000..48848c9
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/feature/Features.java
@@ -0,0 +1,63 @@
+package org.apache.maven.feature;
+
+/*
+ * 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.
+ */
+
+/**
+ * Centralized class for feature information
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public final class Features
+{
+ private Features()
+ {
+ }
+
+ private static final Feature BUILDCONSUMER = new Feature( "maven.experimental.buildconsumer", "true" );
+
+ public static Feature buildConsumer()
+ {
+ return BUILDCONSUMER;
+ }
+
+ /**
+ * Represents some feature
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+ public static class Feature
+ {
+ private final boolean active;
+
+ Feature( String name, String defaultValue )
+ {
+ active = "true".equals( System.getProperty( name, defaultValue ) );
+ }
+
+ public boolean isActive()
+ {
+ return active;
+ }
+
+ }
+
+}
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/AbstractModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/AbstractModelSourceTransformer.java
new file mode 100644
index 0000000..31b88bf
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/AbstractModelSourceTransformer.java
@@ -0,0 +1,199 @@
+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.io.FileInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.maven.xml.Factories;
+import org.apache.maven.xml.sax.ext.CommentRenormalizer;
+import org.apache.maven.xml.sax.filter.AbstractSAXFilter;
+import org.xml.sax.SAXException;
+
+/**
+ * Offers a transformation implementation based on PipelineStreams.
+ * Subclasses are responsible for providing the right SAXFilter.
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public abstract class AbstractModelSourceTransformer
+ implements ModelSourceTransformer
+{
+ private static final AtomicInteger TRANSFORM_THREAD_COUNTER = new AtomicInteger();
+
+ private final TransformerFactory transformerFactory = Factories.newTransformerFactory();
+
+ protected abstract AbstractSAXFilter getSAXFilter( Path pomFile, TransformerContext context )
+ throws TransformerConfigurationException, SAXException, ParserConfigurationException;
+
+ protected OutputStream filterOutputStream( OutputStream outputStream, Path pomFile )
+ {
+ return outputStream;
+ }
+
+ protected TransformerHandler getTransformerHandler( Path pomFile )
+ throws IOException, org.apache.maven.model.building.TransformerException
+ {
+ return null;
+ }
+
+ @Override
+ public final InputStream transform( Path pomFile, TransformerContext context )
+ throws IOException, org.apache.maven.model.building.TransformerException
+ {
+ final TransformerHandler transformerHandler = getTransformerHandler( pomFile );
+
+ final AbstractSAXFilter filter;
+ try
+ {
+ filter = getSAXFilter( pomFile, context );
+ filter.setLexicalHandler( transformerHandler );
+ }
+ catch ( TransformerConfigurationException | SAXException | ParserConfigurationException e )
+ {
+ throw new org.apache.maven.model.building.TransformerException( e );
+ }
+
+ final SAXSource transformSource =
+ new SAXSource( filter,
+ new org.xml.sax.InputSource( new FileInputStream( pomFile.toFile() ) ) );
+
+ final PipedOutputStream pout = new PipedOutputStream();
+ final PipedInputStream pipedInputStream = new PipedInputStream( pout );
+
+ OutputStream out = filterOutputStream( pout, pomFile );
+
+ final javax.xml.transform.Result result;
+ if ( transformerHandler == null )
+ {
+ result = new StreamResult( out );
+ }
+ else
+ {
+ result = new SAXResult( transformerHandler );
+ ( (SAXResult) result ).setLexicalHandler( new CommentRenormalizer( transformerHandler ) );
+ transformerHandler.setResult( new StreamResult( out ) );
+ }
+
+ IOExceptionHandler eh = new IOExceptionHandler();
+
+ Thread transformThread = new Thread( () ->
+ {
+ try ( PipedOutputStream pos = pout )
+ {
+ transformerFactory.newTransformer().transform( transformSource, result );
+ }
+ catch ( TransformerException | IOException e )
+ {
+ eh.uncaughtException( Thread.currentThread(), e );
+ }
+ }, "TransformThread-" + TRANSFORM_THREAD_COUNTER.incrementAndGet() );
+ transformThread.setUncaughtExceptionHandler( eh );
+ transformThread.setDaemon( true );
+ transformThread.start();
+
+ return new ThreadAwareInputStream( pipedInputStream, eh );
+ }
+
+ private static class IOExceptionHandler
+ implements Thread.UncaughtExceptionHandler, AutoCloseable
+ {
+ private volatile Throwable cause;
+
+ @Override
+ public void uncaughtException( Thread t, Throwable e )
+ {
+ try
+ {
+ throw e;
+ }
+ catch ( TransformerException | IOException | RuntimeException | Error allGood )
+ {
+ // all good
+ this.cause = e;
+ }
+ catch ( Throwable notGood )
+ {
+ throw new AssertionError( "Unexpected Exception", e );
+ }
+ }
+
+ @Override
+ public void close()
+ throws IOException
+ {
+ if ( cause != null )
+ {
+ try
+ {
+ throw cause;
+ }
+ catch ( IOException | RuntimeException | Error e )
+ {
+ throw e;
+ }
+ catch ( Throwable t )
+ {
+ // Any checked exception
+ throw new RuntimeException( "Failed to transform pom", t );
+ }
+ }
+ }
+ }
+
+ private class ThreadAwareInputStream
+ extends FilterInputStream
+ {
+ final IOExceptionHandler h;
+
+ protected ThreadAwareInputStream( InputStream in, IOExceptionHandler h )
+ {
+ super( in );
+ this.h = h;
+ }
+
+ @Override
+ public void close()
+ throws IOException
+ {
+ try ( IOExceptionHandler eh = h )
+ {
+ super.close();
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..dbf9211
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java
@@ -0,0 +1,84 @@
+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.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+
+import org.apache.maven.xml.sax.filter.AbstractSAXFilter;
+import org.apache.maven.xml.sax.filter.BuildPomXMLFilterFactory;
+import org.apache.maven.xml.sax.filter.BuildPomXMLFilterListener;
+import org.eclipse.sisu.Nullable;
+import org.xml.sax.SAXException;
+
+/**
+ * ModelSourceTransformer for the build pom
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+@Named
+@Singleton
+class BuildModelSourceTransformer extends AbstractModelSourceTransformer
+{
+ @Inject
+ @Nullable
+ private BuildPomXMLFilterListener xmlFilterListener;
+
+ protected AbstractSAXFilter getSAXFilter( Path pomFile, TransformerContext context )
+ throws TransformerConfigurationException, SAXException, ParserConfigurationException
+ {
+ BuildPomXMLFilterFactory buildPomXMLFilterFactory = new DefaultBuildPomXMLFilterFactory( context );
+
+ return buildPomXMLFilterFactory.get( pomFile );
+ }
+
+ @Override
+ protected OutputStream filterOutputStream( OutputStream outputStream, Path pomFile )
+ {
+ OutputStream out;
+ if ( xmlFilterListener != null )
+ {
+ out = new FilterOutputStream( outputStream )
+ {
+ @Override
+ public void write( byte[] b, int off, int len )
+ throws IOException
+ {
+ super.write( b, off, len );
+ xmlFilterListener.write( pomFile, b, off, len );
+ }
+ };
+ }
+ else
+ {
+ out = outputStream;
+ }
+ return out;
+ }
+}
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
new file mode 100644
index 0000000..3de90de
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java
@@ -0,0 +1,92 @@
+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.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.xml.sax.filter.BuildPomXMLFilterFactory;
+import org.apache.maven.xml.sax.filter.RelativeProject;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class DefaultBuildPomXMLFilterFactory extends BuildPomXMLFilterFactory
+{
+ private final TransformerContext context;
+
+ public DefaultBuildPomXMLFilterFactory( TransformerContext context )
+ {
+ this.context = context;
+ }
+
+ public final TransformerContext getContext()
+ {
+ return context;
+ }
+
+ @Override
+ protected Function<Path, Optional<RelativeProject>> getRelativePathMapper()
+ {
+ return p -> Optional.ofNullable( context.getRawModel( p ) ).map( m -> toRelativeProject( m ) );
+ }
+
+ @Override
+ protected BiFunction<String, String, String> getDependencyKeyToVersionMapper()
+ {
+ return (g, a) -> Optional.ofNullable( context.getRawModel( g, a ) )
+ .map( m -> toVersion( m ) )
+ .orElse( null );
+ }
+
+ private static RelativeProject toRelativeProject( final Model m )
+ {
+ String groupId = m.getGroupId();
+ if ( groupId == null && m.getParent() != null )
+ {
+ groupId = m.getParent().getGroupId();
+ }
+
+ String version = m.getVersion();
+ if ( version == null && m.getParent() != null )
+ {
+ version = m.getParent().getVersion();
+ }
+
+ return new RelativeProject( groupId, m.getArtifactId(), version );
+ }
+
+ private static String toVersion( final Model m )
+ {
+ String version = m.getVersion();
+ if ( version == null && m.getParent() != null )
+ {
+ version = m.getParent().getVersion();
+ }
+
+ return version;
+ }
+}
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 b72550b..77a117d 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
@@ -19,22 +19,49 @@ package org.apache.maven.model.building;
* under the License.
*/
+import static org.apache.maven.model.building.Result.error;
+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.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.building.Source;
+import org.apache.maven.feature.Features;
import org.apache.maven.model.Activation;
import org.apache.maven.model.Build;
+import org.apache.maven.model.BuildBase;
+import org.apache.maven.model.CiManagement;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.InputLocation;
import org.apache.maven.model.InputSource;
import org.apache.maven.model.Model;
+import org.apache.maven.model.ModelBase;
import org.apache.maven.model.Parent;
import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginContainer;
import org.apache.maven.model.PluginManagement;
import org.apache.maven.model.Profile;
+import org.apache.maven.model.ReportPlugin;
+import org.apache.maven.model.Reporting;
import org.apache.maven.model.Repository;
import org.apache.maven.model.building.ModelProblem.Severity;
import org.apache.maven.model.building.ModelProblem.Version;
@@ -44,6 +71,7 @@ import org.apache.maven.model.interpolation.ModelInterpolator;
import org.apache.maven.model.io.ModelParseException;
import org.apache.maven.model.management.DependencyManagementInjector;
import org.apache.maven.model.management.PluginManagementInjector;
+import org.apache.maven.model.merge.ModelMerger;
import org.apache.maven.model.normalization.ModelNormalizer;
import org.apache.maven.model.path.ModelPathTranslator;
import org.apache.maven.model.path.ModelUrlNormalizer;
@@ -64,25 +92,6 @@ import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
import org.eclipse.sisu.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import static org.apache.maven.model.building.Result.error;
-import static org.apache.maven.model.building.Result.newResult;
-
/**
* @author Benjamin Bentmann
*/
@@ -108,7 +117,7 @@ public class DefaultModelBuilder
@Inject
private ModelUrlNormalizer modelUrlNormalizer;
-
+
@Inject
private SuperPomProvider superPomProvider;
@@ -142,6 +151,8 @@ public class DefaultModelBuilder
@Inject
private ReportingConverter reportingConverter;
+
+ private ModelMerger modelMerger = new FileToRawModelMerger();
public DefaultModelBuilder setModelProcessor( ModelProcessor modelProcessor )
{
@@ -244,7 +255,7 @@ public class DefaultModelBuilder
this.reportingConverter = reportingConverter;
return this;
}
-
+
@SuppressWarnings( "checkstyle:methodlength" )
@Override
public ModelBuildingResult build( ModelBuildingRequest request )
@@ -426,7 +437,7 @@ public class DefaultModelBuilder
}
result.setEffectiveModel( resultModel );
-
+
for ( ModelData currentData : lineage )
{
String modelId = ( currentData != superData ) ? currentData.getId() : "";
@@ -529,6 +540,7 @@ public class DefaultModelBuilder
}
}
+ @SuppressWarnings( "checkstyle:methodlength" )
private Model readModel( ModelSource modelSource, File pomFile, ModelBuildingRequest request,
DefaultModelProblemCollector problems )
throws ModelBuildingException
@@ -637,7 +649,6 @@ public class DefaultModelBuilder
.setMessage( "Non-readable POM " + modelSource.getLocation() + ": " + msg ).setException( e ) );
throw problems.newModelBuildingException();
}
-
if ( pomFile != null )
{
model.setPomFile( pomFile );
@@ -646,8 +657,33 @@ public class DefaultModelBuilder
{
model.setPomFile( ( (FileModelSource) modelSource ).getFile() );
}
-
problems.setSource( model );
+
+ modelValidator.validateFileModel( model, request, problems );
+ request.setFileModel( model );
+
+ if ( Features.buildConsumer().isActive() && pomFile != null )
+ {
+ try
+ {
+ Model rawModel =
+ modelProcessor.read( pomFile,
+ Collections.singletonMap( "transformerContext", request.getTransformerContext() ) );
+
+ model.setPomFile( pomFile );
+
+ // model with locationTrackers, required for proper feedback during validations
+ model = request.getFileModel().clone();
+
+ // Apply enriched data
+ modelMerger.merge( model, rawModel, false, null );
+ }
+ catch ( IOException e )
+ {
+ problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V37 ).setException( e ) );
+ }
+ }
+
modelValidator.validateRawModel( model, request, problems );
if ( hasFatalErrors( problems ) )
@@ -953,12 +989,17 @@ public class DefaultModelBuilder
if ( parentData == null )
{
- parentData = fromCache( request.getModelCache(),
- parent.getGroupId(), parent.getArtifactId(),
- parent.getVersion(), ModelCacheTag.RAW );
+ ModelData candidateData = fromCache( request.getModelCache(),
+ parent.getGroupId(), parent.getArtifactId(),
+ parent.getVersion(), ModelCacheTag.RAW );
+
- // ArtifactModelSource means repositorySource
- if ( parentData == null || !( parentData.getSource() instanceof ArtifactModelSource ) )
+ if ( candidateData != null && candidateData.getSource() instanceof ArtifactModelSource )
+ {
+ // ArtifactModelSource means repositorySource
+ parentData = candidateData;
+ }
+ else
{
parentData = readParentExternally( childModel, request, problems );
@@ -967,21 +1008,20 @@ public class DefaultModelBuilder
parentData.getVersion(), ModelCacheTag.RAW, parentData );
}
}
-
- Model parentModel = parentData.getModel();
-
- if ( !"pom".equals( parentModel.getPackaging() ) )
+
+ if ( parentData != null )
{
- problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
- .setMessage( "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint( parentModel )
- + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"" )
- .setLocation( parentModel.getLocation( "packaging" ) ) );
+ Model parentModel = parentData.getModel();
+
+ if ( !"pom".equals( parentModel.getPackaging() ) )
+ {
+ problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
+ .setMessage( "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint( parentModel )
+ + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"" )
+ .setLocation( parentModel.getLocation( "packaging" ) ) );
+ }
}
}
- else
- {
- parentData = null;
- }
return parentData;
}
@@ -1353,7 +1393,7 @@ public class DefaultModelBuilder
final ModelSource importSource;
try
{
- importSource = modelResolver.resolveModel( groupId, artifactId, version );
+ importSource = modelResolver.resolveModel( dependency );
}
catch ( UnresolvableModelException e )
{
@@ -1516,4 +1556,155 @@ public class DefaultModelBuilder
}
}
+ /**
+ * As long as Maven controls the BuildPomXMLFilter, the entities that need merging are known.
+ * All others can simply be copied from source to target to restore the locationTracker
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+ class FileToRawModelMerger extends ModelMerger
+ {
+ @Override
+ protected void mergeBuild_Extensions( Build target, Build source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+
+ @Override
+ protected void mergeBuildBase_Resources( BuildBase target, BuildBase source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeBuildBase_TestResources( BuildBase target, BuildBase source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeCiManagement_Notifiers( CiManagement target, CiManagement source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeDependencyManagement_Dependencies( DependencyManagement target, DependencyManagement source,
+ boolean sourceDominant, Map<Object, Object> context )
+ {
+ Iterator<Dependency> sourceIterator = source.getDependencies().iterator();
+ target.getDependencies().stream().forEach( t -> mergeDependency( t, sourceIterator.next(), sourceDominant,
+ context ) );
+ }
+
+ @Override
+ protected void mergeDependency_Exclusions( Dependency target, Dependency source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeModel_Contributors( Model target, Model source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeModel_Developers( Model target, Model source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeModel_Licenses( Model target, Model source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeModel_MailingLists( Model target, Model source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeModel_Profiles( Model target, Model source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ Iterator<Profile> sourceIterator = source.getProfiles().iterator();
+ target.getProfiles().stream().forEach( t -> mergeProfile( t, sourceIterator.next(), sourceDominant,
+ context ) );
+ }
+
+ @Override
+ protected void mergeModelBase_Dependencies( ModelBase target, ModelBase source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ Iterator<Dependency> sourceIterator = source.getDependencies().iterator();
+ target.getDependencies().stream().forEach( t -> mergeDependency( t, sourceIterator.next(), sourceDominant,
+ context ) );
+ }
+
+ @Override
+ protected void mergeModelBase_PluginRepositories( ModelBase target, ModelBase source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ target.setPluginRepositories( source.getPluginRepositories() );
+ }
+
+ @Override
+ protected void mergeModelBase_Repositories( ModelBase target, ModelBase source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergePlugin_Dependencies( Plugin target, Plugin source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ Iterator<Dependency> sourceIterator = source.getDependencies().iterator();
+ target.getDependencies().stream().forEach( t -> mergeDependency( t, sourceIterator.next(), sourceDominant,
+ context ) );
+ }
+
+ @Override
+ protected void mergePlugin_Executions( Plugin target, Plugin source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeReporting_Plugins( Reporting target, Reporting source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergeReportPlugin_ReportSets( ReportPlugin target, ReportPlugin source, boolean sourceDominant,
+ Map<Object, Object> context )
+ {
+ // don't merge
+ }
+
+ @Override
+ protected void mergePluginContainer_Plugins( PluginContainer target, PluginContainer source,
+ boolean sourceDominant, Map<Object, Object> context )
+ {
+ // don't merge
+ }
+ }
}
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java
index 4240574..daf56ca 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java
@@ -91,7 +91,9 @@ public class DefaultModelBuilderFactory
protected ModelReader newModelReader()
{
- return new DefaultModelReader();
+ DefaultModelReader reader = new DefaultModelReader();
+ reader.setTransformer( newModelSourceTransformer() );
+ return reader;
}
protected ProfileSelector newProfileSelector()
@@ -199,6 +201,11 @@ public class DefaultModelBuilderFactory
return new DefaultReportingConverter();
}
+ private ModelSourceTransformer newModelSourceTransformer()
+ {
+ return new DefaultModelSourceTransformer();
+ }
+
/**
* Creates a new model builder instance.
*
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 84a68f7..2012bb1 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
@@ -38,6 +38,7 @@ import org.apache.maven.model.resolution.WorkspaceModelResolver;
public class DefaultModelBuildingRequest
implements ModelBuildingRequest
{
+ private Model fileModel;
private Model rawModel;
@@ -72,6 +73,8 @@ public class DefaultModelBuildingRequest
private ModelCache modelCache;
private WorkspaceModelResolver workspaceResolver;
+
+ private TransformerContext context;
/**
* Creates an empty request.
@@ -383,6 +386,19 @@ public class DefaultModelBuildingRequest
}
@Override
+ public Model getFileModel()
+ {
+ return fileModel;
+ }
+
+ @Override
+ public ModelBuildingRequest setFileModel( Model fileModel )
+ {
+ this.fileModel = fileModel;
+ return this;
+ }
+
+ @Override
public Model getRawModel()
{
return rawModel;
@@ -407,5 +423,17 @@ public class DefaultModelBuildingRequest
this.workspaceResolver = workspaceResolver;
return this;
}
-
+
+ @Override
+ public TransformerContext getTransformerContext()
+ {
+ return context;
+ }
+
+ @Override
+ public ModelBuildingRequest setTransformerContext( TransformerContext context )
+ {
+ this.context = context;
+ return this;
+ }
}
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelSourceTransformer.java
new file mode 100644
index 0000000..50ad04b
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelSourceTransformer.java
@@ -0,0 +1,43 @@
+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.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * Default ModelSourceTransformer, provides pomFile as inputStream and ignores the context
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class DefaultModelSourceTransformer implements ModelSourceTransformer
+{
+
+ @Override
+ public InputStream transform( Path pomFile, TransformerContext context )
+ throws IOException, TransformerException
+ {
+ return Files.newInputStream( pomFile );
+ }
+
+}
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 a51126f..1dd2643 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
@@ -257,6 +257,19 @@ class FilterModelBuildingRequest
}
@Override
+ public Model getFileModel()
+ {
+ return request.getFileModel();
+ }
+
+ @Override
+ public ModelBuildingRequest setFileModel( Model fileModel )
+ {
+ request.setFileModel( fileModel );
+ return this;
+ }
+
+ @Override
public Model getRawModel()
{
return request.getRawModel();
@@ -281,5 +294,17 @@ class FilterModelBuildingRequest
request.setWorkspaceModelResolver( workspaceResolver );
return this;
}
-
+
+ @Override
+ public TransformerContext getTransformerContext()
+ {
+ return request.getTransformerContext();
+ }
+
+ @Override
+ public ModelBuildingRequest setTransformerContext( TransformerContext context )
+ {
+ request.setTransformerContext( context );
+ return this;
+ }
}
\ No newline at end of file
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 dce0c32..9523f4c 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,6 +63,21 @@ 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
+ * @since 3.7.0
+ */
+ Model getFileModel();
+
+ /**
+ *
+ * @param fileModel
+ * @return This request, never {@code null}.
+ * @since 3.7.0
+ */
+ ModelBuildingRequest setFileModel( Model fileModel );
/**
* Gets the raw model to build. If not set, model source will be used to load raw model.
@@ -334,5 +349,10 @@ public interface ModelBuildingRequest
WorkspaceModelResolver getWorkspaceModelResolver();
ModelBuildingRequest setWorkspaceModelResolver( WorkspaceModelResolver workspaceResolver );
+
+ TransformerContext getTransformerContext();
+ ModelBuildingRequest setTransformerContext( TransformerContext context );
+
+
}
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java
index 2c7a72e..30b6724 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelProblem.java
@@ -50,7 +50,8 @@ public interface ModelProblem
BASE,
V20,
V30,
- V31
+ V31,
+ V37
}
/**
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
new file mode 100644
index 0000000..2aa3ddb
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
@@ -0,0 +1,35 @@
+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.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public interface ModelSourceTransformer
+{
+ 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
new file mode 100644
index 0000000..2f763a2
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerContext.java
@@ -0,0 +1,64 @@
+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 org.apache.maven.model.Model;
+
+/**
+ * Context used to transform a pom file.
+ *
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public interface TransformerContext
+{
+ /**
+ * Key to get the TransformerContext from the SessionData
+ */
+ Object KEY = TransformerContext.class;
+
+ /**
+ * Get the value of the commandline argument {@code -Dkey=value}
+ * @param key
+ * @return
+ */
+ String getUserProperty( String key );
+
+ /**
+ * Get the model based on the path, will be used to resolve the parent based on relativePath
+ *
+ * @param p the path
+ * @return the model, otherwise {@code null}
+ */
+ Model getRawModel( Path p );
+
+ /**
+ * Get the model from the reactor based on the groupId and artifactId, will be used for reactor dependencies
+ *
+ * @param groupId the groupId
+ * @param artifactId the artifactId
+ * @return the model, otherwise {@code null}
+ * @throws IllegalStateException if multiple versions of the same GA are part of the reactor
+ */
+ Model getRawModel( String groupId, String artifactId ) throws IllegalStateException;
+}
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerException.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerException.java
new file mode 100644
index 0000000..6c89d67
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/TransformerException.java
@@ -0,0 +1,40 @@
+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.
+ */
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class TransformerException extends Exception
+{
+
+ public TransformerException( Exception e )
+ {
+ super ( e );
+ }
+
+ public TransformerException( String message, Throwable exception )
+ {
+ super( message, exception );
+ }
+
+}
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 6e78fd0..1bae747 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
@@ -27,11 +27,15 @@ import java.io.Reader;
import java.util.Map;
import java.util.Objects;
+import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.model.InputSource;
import org.apache.maven.model.Model;
+import org.apache.maven.model.building.ModelSourceTransformer;
+import org.apache.maven.model.building.TransformerContext;
+import org.apache.maven.model.building.TransformerException;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.model.io.xpp3.MavenXpp3ReaderEx;
import org.codehaus.plexus.util.ReaderFactory;
@@ -48,18 +52,51 @@ import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
public class DefaultModelReader
implements ModelReader
{
+ @Inject
+ private ModelSourceTransformer transformer;
+ public void setTransformer( ModelSourceTransformer transformer )
+ {
+ this.transformer = transformer;
+ }
+
@Override
public Model read( File input, Map<String, ?> options )
throws IOException
{
Objects.requireNonNull( input, "input cannot be null" );
- Model model = read( new FileInputStream( input ), options );
+ TransformerContext context = null;
+ if ( options != null )
+ {
+ context = (TransformerContext) options.get( "transformerContext" );
+ }
- model.setPomFile( input );
+ final InputStream is;
+ if ( context == null )
+ {
+ is = new FileInputStream( input );
+ }
+ else
+ {
+ try
+ {
+ is = transformer.transform( input.toPath(), context );
+ }
+ catch ( TransformerException e )
+ {
+ throw new IOException( "Failed to transform " + input, e );
+ }
+ }
- return model;
+ try ( InputStream in = is )
+ {
+ Model model = read( is, options );
+
+ model.setPomFile( input );
+
+ return model;
+ }
}
@Override
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
index ad7e3c7..f789333 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
@@ -88,7 +88,7 @@ public class DefaultModelValidator
private final Set<String> validIds = new HashSet<>();
@Override
- public void validateRawModel( Model m, ModelBuildingRequest request, ModelProblemCollector problems )
+ public void validateFileModel( Model m, ModelBuildingRequest request, ModelProblemCollector problems )
{
Parent parent = m.getParent();
if ( parent != null )
@@ -99,9 +99,6 @@ public class DefaultModelValidator
validateStringNotEmpty( "parent.artifactId", problems, Severity.FATAL, Version.BASE, parent.getArtifactId(),
parent );
- validateStringNotEmpty( "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(),
- parent );
-
if ( equals( parent.getGroupId(), m.getGroupId() ) && equals( parent.getArtifactId(), m.getArtifactId() ) )
{
addViolation( problems, Severity.FATAL, Version.BASE, "parent.artifactId", null,
@@ -120,6 +117,17 @@ public class DefaultModelValidator
if ( request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
{
+ Set<String> modules = new HashSet<>();
+ for ( int i = 0, n = m.getModules().size(); i < n; i++ )
+ {
+ String module = m.getModules().get( i );
+ if ( !modules.add( module ) )
+ {
+ addViolation( problems, Severity.ERROR, Version.V20, "modules.module[" + i + "]", null,
+ "specifies duplicate child module " + module, m.getLocation( "modules" ) );
+ }
+ }
+
Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
// [MNG-6074] Maven should produce an error if no model version has been set in a POM file used to build an
@@ -221,6 +229,18 @@ public class DefaultModelValidator
}
}
}
+
+ @Override
+ public void validateRawModel( Model m, ModelBuildingRequest request, ModelProblemCollector problems )
+ {
+ Parent parent = m.getParent();
+
+ if ( parent != null )
+ {
+ validateStringNotEmpty( "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(),
+ parent );
+ }
+ }
private void validate30RawProfileActivation( ModelProblemCollector problems, Activation activation,
String sourceHint, String prefix, String fieldName,
@@ -376,17 +396,6 @@ public class DefaultModelValidator
if ( request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
{
- Set<String> modules = new HashSet<>();
- for ( int i = 0, n = m.getModules().size(); i < n; i++ )
- {
- String module = m.getModules().get( i );
- if ( !modules.add( module ) )
- {
- addViolation( problems, Severity.ERROR, Version.V20, "modules.module[" + i + "]", null,
- "specifies duplicate child module " + module, m.getLocation( "modules" ) );
- }
- }
-
Severity errOn31 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1 );
validateBannedCharacters( EMPTY, "version", problems, errOn31, Version.V20, m.getVersion(), null, m,
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/validation/ModelValidator.java b/maven-model-builder/src/main/java/org/apache/maven/model/validation/ModelValidator.java
index 84e3fad..198ba5a 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/validation/ModelValidator.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/validation/ModelValidator.java
@@ -30,15 +30,27 @@ import org.apache.maven.model.building.ModelProblemCollector;
*/
public interface ModelValidator
{
-
/**
- * Checks the specified (raw) model for missing or invalid values. The raw model is directly created from the POM
+ * Checks the specified file model for missing or invalid values. This model is directly created from the POM
* file and has not been subjected to inheritance, interpolation or profile/default injection.
*
* @param model The model to validate, must not be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
+ default void validateFileModel( Model model, ModelBuildingRequest request, ModelProblemCollector problems )
+ {
+ // do nothing
+ }
+
+ /**
+ * Checks the specified (raw) model for missing or invalid values. The raw model is the file model + buildpom filter
+ * transformation and has not been subjected to inheritance, interpolation or profile/default injection.
+ *
+ * @param model The model to validate, must not be {@code null}.
+ * @param request The model building request that holds further settings, must not be {@code null}.
+ * @param problems The container used to collect problems that were encountered, must not be {@code null}.
+ */
void validateRawModel( Model model, ModelBuildingRequest request, ModelProblemCollector problems );
/**
diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
new file mode 100644
index 0000000..485dc4c
--- /dev/null
+++ b/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
@@ -0,0 +1,82 @@
+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 static org.hamcrest.Matchers.hasItems;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.maven.model.building.DefaultModelBuilder.FileToRawModelMerger;
+import org.apache.maven.model.merge.ModelMerger;
+import org.junit.Test;
+
+public class FileToRawModelMergerTest
+{
+
+ /**
+ * Ensures that all list-merge methods are overridden
+ */
+ @Test
+ public void testOverriddenMergeMethods()
+ {
+ List<String> methodNames =
+ Stream.of( ModelMerger.class.getDeclaredMethods() )
+ .filter( m -> m.getName().startsWith( "merge" ) )
+ .filter( m ->
+ {
+ String baseName = m.getName().substring( 5 /* merge */ );
+ String entity = baseName.substring( baseName.indexOf( '_' ) + 1 );
+ try
+ {
+ Type returnType = m.getParameterTypes()[0].getMethod( "get" + entity ).getGenericReturnType();
+ if ( returnType instanceof ParameterizedType )
+ {
+ return !( (ParameterizedType) returnType ).getActualTypeArguments()[0].equals( String.class );
+ }
+ else
+ {
+ return false;
+ }
+ }
+ catch ( ReflectiveOperationException | SecurityException e )
+ {
+ return false;
+ }
+ } )
+ .map( Method::getName )
+ .collect( Collectors.toList() );
+
+ List<String> overriddenMethods =
+ Stream.of( FileToRawModelMerger.class.getDeclaredMethods() )
+ .map( Method::getName )
+ .filter( m -> m.startsWith( "merge" ) )
+ .collect( Collectors.toList() );
+
+ assertThat( overriddenMethods, hasItems( methodNames.toArray( new String[0] ) ) );
+ }
+
+
+}
diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java
index 09f930c..9924471 100644
--- a/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java
+++ b/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java
@@ -20,18 +20,24 @@ package org.apache.maven.model.inheritance;
*/
import org.apache.maven.model.Model;
+import org.apache.maven.model.building.AbstractModelSourceTransformer;
import org.apache.maven.model.building.SimpleProblemCollector;
+import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.io.DefaultModelReader;
import org.apache.maven.model.io.DefaultModelWriter;
-import org.apache.maven.model.io.ModelReader;
import org.apache.maven.model.io.ModelWriter;
-
+import org.apache.maven.xml.sax.filter.AbstractSAXFilter;
+import org.xml.sax.SAXException;
import org.xmlunit.matchers.CompareMatcher;
import junit.framework.TestCase;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
import static org.junit.Assert.assertThat;
@@ -41,7 +47,7 @@ import static org.junit.Assert.assertThat;
public class DefaultInheritanceAssemblerTest
extends TestCase
{
- private ModelReader reader;
+ private DefaultModelReader reader;
private ModelWriter writer;
@@ -54,6 +60,15 @@ public class DefaultInheritanceAssemblerTest
super.setUp();
reader = new DefaultModelReader();
+ reader.setTransformer( new AbstractModelSourceTransformer()
+ {
+ @Override
+ protected AbstractSAXFilter getSAXFilter( Path pomFile, TransformerContext context )
+ throws TransformerConfigurationException, SAXException, ParserConfigurationException
+ {
+ return null;
+ }
+ } );
writer = new DefaultModelWriter();
assembler = new DefaultInheritanceAssembler();
}
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 3e07c57..d2a9e60 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
@@ -64,10 +64,14 @@ public class DefaultModelValidatorTest
throws Exception
{
ModelBuildingRequest request = new DefaultModelBuildingRequest().setValidationLevel( level );
+
+ Model model = read( pom );
- SimpleProblemCollector problems = new SimpleProblemCollector( read( pom ) );
+ SimpleProblemCollector problems = new SimpleProblemCollector( model );
+
+ request.setFileModel( model );
- validator.validateEffectiveModel( problems.getModel(), request, problems );
+ validator.validateEffectiveModel( model, request, problems );
return problems;
}
@@ -77,9 +81,15 @@ public class DefaultModelValidatorTest
{
ModelBuildingRequest request = new DefaultModelBuildingRequest().setValidationLevel( level );
- SimpleProblemCollector problems = new SimpleProblemCollector( read( pom ) );
+ Model model = read( pom );
+
+ SimpleProblemCollector problems = new SimpleProblemCollector( model );
- validator.validateRawModel( problems.getModel(), request, problems );
+ validator.validateFileModel( model, request, problems );
+
+ request.setFileModel( model );
+
+ validator.validateRawModel( model, request, problems );
return problems;
}
@@ -366,7 +376,7 @@ public class DefaultModelValidatorTest
public void testDuplicateModule()
throws Exception
{
- SimpleProblemCollector result = validate( "duplicate-module.xml" );
+ SimpleProblemCollector result = validateRaw( "duplicate-module.xml" );
assertViolations( result, 0, 1, 0 );
@@ -416,7 +426,6 @@ public class DefaultModelValidatorTest
SimpleProblemCollector result = validateRaw( "incomplete-parent.xml" );
assertViolations( result, 3, 0, 0 );
-
assertTrue( result.getFatals().get( 0 ).contains( "parent.groupId" ) );
assertTrue( result.getFatals().get( 1 ).contains( "parent.artifactId" ) );
assertTrue( result.getFatals().get( 2 ).contains( "parent.version" ) );
diff --git a/maven-xml/pom.xml b/maven-xml/pom.xml
new file mode 100644
index 0000000..6b98f12
--- /dev/null
+++ b/maven-xml/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven</artifactId>
+ <version>3.7.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>maven-xml</artifactId>
+ <name>Maven XML</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-assertj</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/Factories.java b/maven-xml/src/main/java/org/apache/maven/xml/Factories.java
new file mode 100644
index 0000000..eb2166a
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/Factories.java
@@ -0,0 +1,118 @@
+package org.apache.maven.xml;
+
+/*
+ * 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 javax.xml.XMLConstants;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.TransformerFactory;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+
+/**
+ * Creates XML related factories with OWASP advices applied
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public final class Factories
+{
+ private Factories()
+ {
+ }
+
+ /**
+ *
+ * @return
+ * @see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#transformerfactory
+ */
+ public static TransformerFactory newTransformerFactory()
+ {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_DTD, "" );
+ tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "" );
+
+ return tf;
+ }
+
+ public static SAXParserFactory newSAXParserFactory()
+ {
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+
+ try
+ {
+ // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
+ // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
+
+ // Using the SAXParserFactory's setFeature
+ spf.setFeature( "http://xml.org/sax/features/external-general-entities", false );
+
+ // Xerces 2 only - http://xerces.apache.org/xerces-j/features.html#external-general-entities
+ spf.setFeature( "http://apache.org/xml/features/disallow-doctype-decl", true );
+ }
+ catch ( ParserConfigurationException e )
+ {
+ // Tried an unsupported feature.
+ }
+ catch ( SAXNotRecognizedException e )
+ {
+ // Tried an unknown feature.
+ }
+ catch ( SAXNotSupportedException e )
+ {
+ // Tried a feature known to the parser but unsupported.
+ }
+ return spf;
+ }
+
+ public static SAXParser newSAXParser() throws ParserConfigurationException, SAXException
+ {
+ SAXParser saxParser = newSAXParserFactory().newSAXParser();
+
+ return saxParser;
+ }
+
+ public static XMLReader newXMLReader() throws SAXException, ParserConfigurationException
+ {
+ XMLReader reader = newSAXParser().getXMLReader();
+
+ try
+ {
+ // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
+ // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
+
+ // Using the XMLReader's setFeature
+ reader.setFeature( "http://xml.org/sax/features/external-general-entities", false );
+ }
+ catch ( SAXNotRecognizedException e )
+ {
+ // Tried an unknown feature.
+ }
+ catch ( SAXNotSupportedException e )
+ {
+ // Tried a feature known to the parser but unsupported.
+ }
+ return reader;
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEvent.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEvent.java
new file mode 100644
index 0000000..8126957
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEvent.java
@@ -0,0 +1,34 @@
+package org.apache.maven.xml.sax;
+
+/*
+ * 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 org.xml.sax.SAXException;
+
+/**
+ * Command pattern to gather events which can be executed later on.
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+@FunctionalInterface
+public interface SAXEvent
+{
+ void execute() throws SAXException;
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEventFactory.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEventFactory.java
new file mode 100644
index 0000000..84e13e5
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEventFactory.java
@@ -0,0 +1,144 @@
+package org.apache.maven.xml.sax;
+
+/*
+ * 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 org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Factory for SAXEvents
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public final class SAXEventFactory
+{
+ private final ContentHandler contentHandler;
+
+ private final LexicalHandler lexicalHandler;
+
+ protected SAXEventFactory( ContentHandler contentHandler, LexicalHandler lexicalHandler )
+ {
+ this.contentHandler = contentHandler;
+ this.lexicalHandler = lexicalHandler;
+ }
+
+ public SAXEvent characters( final char[] ch, final int start, final int length )
+ {
+ final char[] txt = new char[length];
+ System.arraycopy( ch, start, txt, 0, length );
+ return () -> contentHandler.characters( txt, 0, length );
+ }
+
+ public SAXEvent endDocument()
+ {
+ return () -> contentHandler.endDocument();
+ }
+
+ public SAXEvent endElement( final String uri, final String localName, final String qName )
+ {
+ return () -> contentHandler.endElement( uri, localName, qName );
+ }
+
+ public SAXEvent endPrefixMapping( final String prefix )
+ {
+ return () -> contentHandler.endPrefixMapping( prefix );
+ }
+
+ public SAXEvent ignorableWhitespace( final char[] ch, final int start, final int length )
+ {
+ return () -> contentHandler.ignorableWhitespace( ch, start, length );
+ }
+
+ public SAXEvent processingInstruction( final String target, final String data )
+ {
+ return () -> contentHandler.processingInstruction( target, data );
+ }
+
+ public SAXEvent setDocumentLocator( final Locator locator )
+ {
+ return () -> contentHandler.setDocumentLocator( locator );
+ }
+
+ public SAXEvent skippedEntity( final String name )
+ {
+ return () -> contentHandler.skippedEntity( name );
+ }
+
+ public SAXEvent startDocument()
+ {
+ return () -> contentHandler.startDocument();
+ }
+
+ public SAXEvent startElement( final String uri, final String localName, final String qName, final Attributes atts )
+ {
+ return () -> contentHandler.startElement( uri, localName, qName, atts );
+ }
+
+ public SAXEvent startPrefixMapping( final String prefix, final String uri )
+ {
+ return () -> contentHandler.startPrefixMapping( prefix, uri );
+ }
+
+ public static SAXEventFactory newInstance( ContentHandler contentHandler, LexicalHandler lexicalHandler )
+ {
+ return new SAXEventFactory( contentHandler, lexicalHandler );
+ }
+
+ public SAXEvent startDTD( String name, String publicId, String systemId )
+ {
+ return () -> lexicalHandler.startDTD( name, publicId, systemId );
+ }
+
+ public SAXEvent endDTD()
+ {
+ return () -> lexicalHandler.endDTD();
+ }
+
+ public SAXEvent startEntity( String name )
+ {
+ return () -> lexicalHandler.startEntity( name );
+ }
+
+ public SAXEvent endEntity( String name )
+ {
+ return () -> lexicalHandler.endEntity( name );
+
+ }
+
+ public SAXEvent startCDATA()
+ {
+ return () -> lexicalHandler.startCDATA();
+ }
+
+ public SAXEvent endCDATA()
+ {
+ return () -> lexicalHandler.endCDATA();
+ }
+
+ public SAXEvent comment( char[] ch, int start, int length )
+ {
+ final char[] txt = new char[length];
+ System.arraycopy( ch, start, txt, 0, length );
+ return () -> lexicalHandler.comment( txt, 0, length );
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEventUtils.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEventUtils.java
new file mode 100644
index 0000000..237ec44
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/SAXEventUtils.java
@@ -0,0 +1,49 @@
+package org.apache.maven.xml.sax;
+
+/*
+ * 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.util.regex.Pattern;
+
+/**
+ * Utility class for SAXEvents
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public final class SAXEventUtils
+{
+ private static final Pattern PATTERN = Pattern.compile( "[^:]+$" );
+
+ private SAXEventUtils()
+ {
+ }
+
+ /**
+ * Returns the newLocalName prefixed with the namespace of the oldQName if present
+ *
+ * @param oldQName the QName, used for its namespace
+ * @param newLocalName the preferred localName
+ * @return the new QName
+ */
+ public static String renameQName( String oldQName, String newLocalName )
+ {
+ return PATTERN.matcher( oldQName ).replaceFirst( newLocalName );
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/ext/CommentRenormalizer.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/ext/CommentRenormalizer.java
new file mode 100644
index 0000000..3ae19a4
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/ext/CommentRenormalizer.java
@@ -0,0 +1,108 @@
+package org.apache.maven.xml.sax.ext;
+
+/*
+ * 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 org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * During parsing the line separators are transformed to \n
+ * Unlike characters(), comments don't use the systems line separator for serialization.
+ * Hence use this class in the LexicalHandler chain to do so
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class CommentRenormalizer implements LexicalHandler
+{
+ private final LexicalHandler lexicalHandler;
+
+ private final String lineSeparator;
+
+ public CommentRenormalizer( LexicalHandler lexicalHandler )
+ {
+ this( lexicalHandler, System.lineSeparator() );
+ }
+
+ // for testing purpose
+ CommentRenormalizer( LexicalHandler lexicalHandler, String lineSeparator )
+ {
+ this.lexicalHandler = lexicalHandler;
+ this.lineSeparator = lineSeparator;
+ }
+
+ @Override
+ public void comment( char[] ch, int start, int length )
+ throws SAXException
+ {
+ if ( "\n".equals( lineSeparator ) )
+ {
+ lexicalHandler.comment( ch, start, length );
+ }
+ else
+ {
+ char[] ca = new String( ch, start, length ).replaceAll( "\n", lineSeparator ).toCharArray();
+
+ lexicalHandler.comment( ca, 0, ca.length );
+ }
+ }
+
+ @Override
+ public void startDTD( String name, String publicId, String systemId )
+ throws SAXException
+ {
+ lexicalHandler.startDTD( name, publicId, systemId );
+ }
+
+ @Override
+ public void endDTD()
+ throws SAXException
+ {
+ lexicalHandler.endDTD();
+ }
+
+ @Override
+ public void startEntity( String name )
+ throws SAXException
+ {
+ lexicalHandler.startEntity( name );
+ }
+
+ @Override
+ public void endEntity( String name )
+ throws SAXException
+ {
+ lexicalHandler.endEntity( name );
+ }
+
+ @Override
+ public void startCDATA()
+ throws SAXException
+ {
+ lexicalHandler.startCDATA();
+ }
+
+ @Override
+ public void endCDATA()
+ throws SAXException
+ {
+ lexicalHandler.endCDATA();
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/AbstractEventXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/AbstractEventXMLFilter.java
new file mode 100644
index 0000000..d23bdec
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/AbstractEventXMLFilter.java
@@ -0,0 +1,289 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.util.ArrayDeque;
+import java.util.Queue;
+
+import org.apache.maven.xml.sax.SAXEvent;
+import org.apache.maven.xml.sax.SAXEventFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Builds up a list of SAXEvents, which will be executed with {@link #executeEvents()}
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+abstract class AbstractEventXMLFilter extends AbstractSAXFilter
+{
+ private Queue<SAXEvent> saxEvents = new ArrayDeque<>();
+
+ private SAXEventFactory eventFactory;
+
+ // characters BEFORE startElement must get state of startingElement
+ // this way removing based on state keeps correct formatting
+ private SAXEvent characters;
+
+ private boolean lockCharacters = false;
+
+ protected abstract boolean isParsing();
+
+ protected abstract String getState();
+
+ protected boolean acceptEvent( String state )
+ {
+ return true;
+ }
+
+ AbstractEventXMLFilter()
+ {
+ super();
+ }
+
+ <T extends XMLReader & LexicalHandler> AbstractEventXMLFilter( T parent )
+ {
+ setParent( parent );
+ }
+
+ private SAXEventFactory getEventFactory()
+ {
+ if ( eventFactory == null )
+ {
+ eventFactory = SAXEventFactory.newInstance( getContentHandler(), getLexicalHandler() );
+ }
+ return eventFactory;
+ }
+
+ private void processEvent( final SAXEvent event )
+ throws SAXException
+ {
+ if ( isParsing() )
+ {
+ final String eventState = getState();
+ final SAXEvent charactersEvent = characters;
+
+ if ( !lockCharacters && charactersEvent != null )
+ {
+ saxEvents.add( () ->
+ {
+ if ( acceptEvent( eventState ) )
+ {
+ charactersEvent.execute();
+ }
+ } );
+ characters = null;
+ }
+
+ saxEvents.add( () ->
+ {
+ if ( acceptEvent( eventState ) )
+ {
+ event.execute();
+ }
+ } );
+ }
+ else
+ {
+ event.execute();
+ }
+ }
+
+ /**
+ * Should be used to include extra events before a closing element.
+ * This is a lightweight solution to keep the correct indentation.
+ *
+ * @return
+ */
+ protected Includer include()
+ {
+ this.lockCharacters = true;
+
+ return () -> lockCharacters = false;
+ }
+
+ protected final void executeEvents() throws SAXException
+ {
+ final String eventState = getState();
+ final SAXEvent charactersEvent = characters;
+ if ( charactersEvent != null )
+ {
+ saxEvents.add( () ->
+ {
+ if ( acceptEvent( eventState ) )
+ {
+ charactersEvent.execute();
+ }
+ } );
+ characters = null;
+ }
+
+ // not with streams due to checked SAXException
+ while ( !saxEvents.isEmpty() )
+ {
+ saxEvents.poll().execute();
+ }
+ }
+
+ @Override
+ public void setDocumentLocator( Locator locator )
+ {
+ try
+ {
+ processEvent( getEventFactory().setDocumentLocator( locator ) );
+ }
+ catch ( SAXException e )
+ {
+ // noop, setDocumentLocator can never throw a SAXException
+ }
+ }
+
+ @Override
+ public void startDocument() throws SAXException
+ {
+ processEvent( getEventFactory().startDocument() );
+ }
+
+ @Override
+ public void endDocument() throws SAXException
+ {
+ processEvent( getEventFactory().endDocument() );
+ }
+
+ @Override
+ public void startPrefixMapping( String prefix, String uri ) throws SAXException
+ {
+ processEvent( getEventFactory().startPrefixMapping( prefix, uri ) );
+ }
+
+ @Override
+ public void endPrefixMapping( String prefix ) throws SAXException
+ {
+ processEvent( getEventFactory().endPrefixMapping( prefix ) );
+ }
+
+ @Override
+ public void startElement( String uri, String localName, String qName, Attributes atts ) throws SAXException
+ {
+ processEvent( getEventFactory().startElement( uri, localName, qName, atts ) );
+ }
+
+ @Override
+ public void endElement( String uri, String localName, String qName ) throws SAXException
+ {
+ processEvent( getEventFactory().endElement( uri, localName, qName ) );
+ }
+
+ @Override
+ public void characters( char[] ch, int start, int length ) throws SAXException
+ {
+ if ( lockCharacters )
+ {
+ processEvent( getEventFactory().characters( ch, start, length ) );
+ }
+ else if ( isParsing() )
+ {
+ this.characters = getEventFactory().characters( ch, start, length );
+ }
+ else
+ {
+ super.characters( ch, start, length );
+ }
+ }
+
+ @Override
+ public void ignorableWhitespace( char[] ch, int start, int length ) throws SAXException
+ {
+ processEvent( getEventFactory().ignorableWhitespace( ch, start, length ) );
+ }
+
+ @Override
+ public void processingInstruction( String target, String data ) throws SAXException
+ {
+ processEvent( getEventFactory().processingInstruction( target, data ) );
+ }
+
+ @Override
+ public void skippedEntity( String name ) throws SAXException
+ {
+ processEvent( getEventFactory().skippedEntity( name ) );
+ }
+
+ @Override
+ public void startDTD( String name, String publicId, String systemId ) throws SAXException
+ {
+ processEvent( getEventFactory().startCDATA() );
+ }
+
+ @Override
+ public void endDTD() throws SAXException
+ {
+ processEvent( getEventFactory().endDTD() );
+ }
+
+ @Override
+ public void startEntity( String name ) throws SAXException
+ {
+ processEvent( getEventFactory().startEntity( name ) );
+ }
+
+ @Override
+ public void endEntity( String name ) throws SAXException
+ {
+ processEvent( getEventFactory().endEntity( name ) );
+ }
+
+ @Override
+ public void startCDATA()
+ throws SAXException
+ {
+ processEvent( getEventFactory().startCDATA() );
+ }
+
+ @Override
+ public void endCDATA()
+ throws SAXException
+ {
+ processEvent( getEventFactory().endCDATA() );
+ }
+
+ @Override
+ public void comment( char[] ch, int start, int length )
+ throws SAXException
+ {
+ processEvent( getEventFactory().comment( ch, start, length ) );
+ }
+
+ /**
+ * AutoCloseable with a close method that doesn't throw an exception
+ *
+ * @author Robert Scholte
+ *
+ */
+ @FunctionalInterface
+ protected interface Includer extends AutoCloseable
+ {
+ void close();
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/AbstractSAXFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/AbstractSAXFilter.java
new file mode 100644
index 0000000..89de519
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/AbstractSAXFilter.java
@@ -0,0 +1,130 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+/**
+ * XMLFilter with LexicalHandler.
+ * Since some filters collect events before processing them, the LexicalHandler events must be collected too.
+ * Otherwise the LexicalHandler events might end up before all collected XMLReader events.
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class AbstractSAXFilter extends XMLFilterImpl implements LexicalHandler
+{
+ private LexicalHandler lexicalHandler;
+
+ AbstractSAXFilter()
+ {
+ super();
+ }
+
+ public <T extends XMLReader & LexicalHandler> AbstractSAXFilter( T parent )
+ {
+ setParent( parent );
+ setLexicalHandler( parent );
+ }
+
+ public LexicalHandler getLexicalHandler()
+ {
+ return lexicalHandler;
+ }
+
+ public void setLexicalHandler( LexicalHandler lexicalHandler )
+ {
+ this.lexicalHandler = lexicalHandler;
+ }
+
+ @Override
+ public void startDTD( String name, String publicId, String systemId )
+ throws SAXException
+ {
+ if ( lexicalHandler != null )
+ {
+ lexicalHandler.startDTD( name, publicId, systemId );
+ }
+ }
+
+ @Override
+ public void endDTD()
+ throws SAXException
+ {
+ if ( lexicalHandler != null )
+ {
+ lexicalHandler.endDTD();
+ }
+ }
+
+ @Override
+ public void startEntity( String name )
+ throws SAXException
+ {
+ if ( lexicalHandler != null )
+ {
+ lexicalHandler.startEntity( name );
+ }
+ }
+
+ @Override
+ public void endEntity( String name )
+ throws SAXException
+ {
+ if ( lexicalHandler != null )
+ {
+ lexicalHandler.endEntity( name );
+ }
+ }
+
+ @Override
+ public void startCDATA()
+ throws SAXException
+ {
+ if ( lexicalHandler != null )
+ {
+ lexicalHandler.startCDATA();
+ }
+ }
+
+ @Override
+ public void endCDATA()
+ throws SAXException
+ {
+ if ( lexicalHandler != null )
+ {
+ lexicalHandler.endCDATA();
+ }
+ }
+
+ @Override
+ public void comment( char[] ch, int start, int length )
+ throws SAXException
+ {
+ if ( lexicalHandler != null )
+ {
+ lexicalHandler.comment( ch, start, length );
+ }
+ }
+
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilter.java
new file mode 100644
index 0000000..14bcf70
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilter.java
@@ -0,0 +1,58 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Filter to adjust pom on filesystem before being processed for effective pom.
+ * There should only be 1 BuildPomXMLFilter, so the same is being used by both
+ * org.apache.maven.model.building.DefaultModelBuilder.transformData(InputStream) and
+ * org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory.newFileTransformerManager()
+ *
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class BuildPomXMLFilter extends AbstractSAXFilter
+{
+ BuildPomXMLFilter()
+ {
+ super();
+ }
+
+ <T extends XMLReader & LexicalHandler> BuildPomXMLFilter( T parent )
+ {
+ super( parent );
+ }
+
+ /**
+ * Don't allow overwriting parent
+ */
+ @Override
+ public final void setParent( XMLReader parent )
+ {
+ if ( getParent() == null )
+ {
+ super.setParent( parent );
+ }
+ }
+}
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
new file mode 100644
index 0000000..6f3f319
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterFactory.java
@@ -0,0 +1,112 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+
+import org.apache.maven.xml.Factories;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Base implementation for providing the BuildPomXML.
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class BuildPomXMLFilterFactory
+{
+ /**
+ *
+ * @param projectFile will be used by ConsumerPomXMLFilter to get the right filter
+ * @return
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws TransformerConfigurationException
+ */
+ public final BuildPomXMLFilter get( Path projectFile )
+ throws SAXException, ParserConfigurationException, TransformerConfigurationException
+ {
+ AbstractSAXFilter parent = new AbstractSAXFilter();
+ parent.setParent( getXMLReader() );
+ parent.setLexicalHandler( getLexicalHander() );
+
+ if ( getDependencyKeyToVersionMapper() != null )
+ {
+ ReactorDependencyXMLFilter reactorDependencyXMLFilter =
+ new ReactorDependencyXMLFilter( getDependencyKeyToVersionMapper() );
+ reactorDependencyXMLFilter.setParent( parent );
+ reactorDependencyXMLFilter.setLexicalHandler( parent );
+ parent = reactorDependencyXMLFilter;
+ }
+
+ if ( getRelativePathMapper() != null )
+ {
+ ParentXMLFilter parentFilter = new ParentXMLFilter( getRelativePathMapper() );
+ parentFilter.setProjectPath( projectFile.getParent() );
+ parentFilter.setParent( parent );
+ parentFilter.setLexicalHandler( parent );
+ parent = parentFilter;
+ }
+
+ return new BuildPomXMLFilter( parent );
+ }
+
+ private XMLReader getXMLReader() throws SAXException, ParserConfigurationException
+ {
+ XMLReader xmlReader = Factories.newXMLReader();
+ xmlReader.setFeature( "http://xml.org/sax/features/namespaces", true );
+ return xmlReader;
+ }
+
+ private LexicalHandler getLexicalHander() throws TransformerConfigurationException
+ {
+ TransformerFactory transformerFactory = Factories.newTransformerFactory();
+ if ( transformerFactory instanceof SAXTransformerFactory )
+ {
+ SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) transformerFactory;
+ return saxTransformerFactory.newTransformerHandler();
+ }
+ throw new TransformerConfigurationException( "Failed to get LexicalHandler via TransformerFactory:"
+ + " it is not an instance of SAXTransformerFactory" );
+ }
+
+ /**
+ * @return the mapper or {@code null} if relativePaths don't need to be mapped
+ */
+ protected Function<Path, Optional<RelativeProject>> getRelativePathMapper()
+ {
+ return null;
+ }
+
+ protected BiFunction<String, String, String> getDependencyKeyToVersionMapper()
+ {
+ return null;
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterListener.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterListener.java
new file mode 100644
index 0000000..b97c757
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/BuildPomXMLFilterListener.java
@@ -0,0 +1,42 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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;
+
+/**
+ * Listener can be used to capture the result of the build pom
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+@FunctionalInterface
+public interface BuildPomXMLFilterListener
+{
+ /**
+ * Captures the result of the XML transformation
+ *
+ * @param pomFile the original to being transformed
+ * @param b the byte array
+ * @param off the offset
+ * @param len the length
+ */
+ void write( Path pomFile, byte[] b, int off, int len );
+}
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
new file mode 100644
index 0000000..a13e4d4
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/CiFriendlyXMLFilter.java
@@ -0,0 +1,83 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.util.function.Function;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Resolves all ci-friendly properties occurrences
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+class CiFriendlyXMLFilter
+ extends AbstractSAXFilter
+{
+ private Function<String, String> replaceChain = Function.identity();
+
+ public CiFriendlyXMLFilter setChangelist( String changelist )
+ {
+ replaceChain = replaceChain.andThen( t -> t.replace( "${changelist}", changelist ) );
+ return this;
+ }
+
+ public CiFriendlyXMLFilter setRevision( String revision )
+ {
+ replaceChain = replaceChain.andThen( t -> t.replace( "${revision}", revision ) );
+ return this;
+ }
+
+ public CiFriendlyXMLFilter setSha1( String sha1 )
+ {
+ replaceChain = replaceChain.andThen( t -> t.replace( "${sha1}", sha1 ) );
+ return this;
+ }
+
+ /**
+ * @return {@code true} is any of the ci properties is set, otherwise {@code false}
+ */
+ public boolean isSet()
+ {
+ return !replaceChain.equals( Function.identity() );
+ }
+
+ @Override
+ public void characters( char[] ch, int start, int length )
+ throws SAXException
+ {
+ String text = new String( ch, start, length );
+
+ // assuming this has the best performance
+ if ( text.contains( "${" ) )
+ {
+ String newText = replaceChain.apply( text );
+
+ super.characters( newText.toCharArray(), 0, newText.length() );
+ }
+ else
+ {
+ super.characters( ch, start, length );
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilter.java
new file mode 100644
index 0000000..1c227a7
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilter.java
@@ -0,0 +1,54 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * XML Filter to transform pom.xml to consumer pom.
+ * This often means stripping of build-specific information.
+ * When extra information is required during filtering it is probably a member of the BuildPomXMLFilter
+ *
+ * This filter is used at 1 locations:
+ * - {@link org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory} when publishing pom files.
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class ConsumerPomXMLFilter extends AbstractSAXFilter
+{
+ <T extends XMLReader & LexicalHandler> ConsumerPomXMLFilter( T filter )
+ {
+ super( filter );
+ }
+
+ /**
+ * Don't allow overwriting parent
+ */
+ @Override
+ public final void setParent( XMLReader parent )
+ {
+ if ( getParent() == null )
+ {
+ super.setParent( parent );
+ }
+ }
+}
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
new file mode 100644
index 0000000..f7751d2
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterFactory.java
@@ -0,0 +1,89 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.Optional;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class ConsumerPomXMLFilterFactory
+{
+ private BuildPomXMLFilterFactory buildPomXMLFilterFactory;
+
+ public ConsumerPomXMLFilterFactory( BuildPomXMLFilterFactory buildPomXMLFilterFactory )
+ {
+ this.buildPomXMLFilterFactory = buildPomXMLFilterFactory;
+ }
+
+ public final ConsumerPomXMLFilter get( Path projectPath )
+ throws SAXException, ParserConfigurationException, TransformerConfigurationException
+ {
+ 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();
+ getChangelist().ifPresent( ciFriendlyFilter::setChangelist );
+ getRevision().ifPresent( ciFriendlyFilter::setRevision );
+ getSha1().ifPresent( ciFriendlyFilter::setSha1 );
+
+ if ( ciFriendlyFilter.isSet() )
+ {
+ ciFriendlyFilter.setParent( parent );
+ ciFriendlyFilter.setLexicalHandler( parent );
+ filter = ciFriendlyFilter;
+ }
+
+ // Strip modules
+ filter = new ModulesXMLFilter( filter );
+ // Adjust relativePath
+ filter = new RelativePathXMLFilter( filter );
+
+ 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/DependencyKey.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/DependencyKey.java
new file mode 100644
index 0000000..1168351
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/DependencyKey.java
@@ -0,0 +1,92 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.util.Objects;
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class DependencyKey
+{
+ private final String groupId;
+
+ private final String artifactId;
+
+ private final int hashCode;
+
+ public DependencyKey( String groupId, String artifactId )
+ {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+
+ this.hashCode = Objects.hash( artifactId, groupId );
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public String getArtifactId()
+ {
+ return artifactId;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ {
+ return true;
+ }
+ if ( obj == null )
+ {
+ return false;
+ }
+ if ( getClass() != obj.getClass() )
+ {
+ return false;
+ }
+
+ DependencyKey other = (DependencyKey) obj;
+
+ if ( !Objects.equals( artifactId, other.artifactId ) )
+ {
+ return false;
+ }
+ if ( !Objects.equals( groupId, other.groupId ) )
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/FastForwardFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/FastForwardFilter.java
new file mode 100644
index 0000000..0100e6b
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/FastForwardFilter.java
@@ -0,0 +1,128 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.util.ArrayDeque;
+import java.util.Deque;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * This filter will skip all following filters and write directly to the output.
+ * Should be used in case of a DOM that should not be effected by other filters, even though the elements match
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+class FastForwardFilter extends AbstractSAXFilter
+{
+ /**
+ * DOM elements of pom
+ *
+ * <ul>
+ * <li>execution.configuration</li>
+ * <li>plugin.configuration</li>
+ * <li>plugin.goals</li>
+ * <li>profile.reports</li>
+ * <li>project.reports</li>
+ * <li>reportSet.configuration</li>
+ * <ul>
+ */
+ private final Deque<String> state = new ArrayDeque<>();
+
+ private int domDepth = 0;
+
+ private ContentHandler originalHandler;
+
+ FastForwardFilter()
+ {
+ super();
+ }
+
+ <T extends XMLReader & LexicalHandler> FastForwardFilter( T parent )
+ {
+ super( parent );
+ }
+
+ @Override
+ public void startElement( String uri, String localName, String qName, Attributes atts )
+ throws SAXException
+ {
+ super.startElement( uri, localName, qName, atts );
+ if ( domDepth > 0 )
+ {
+ domDepth++;
+ }
+ else
+ {
+ final String key = state.peek() + '.' + localName;
+ switch ( key )
+ {
+ case "execution.configuration":
+ case "plugin.configuration":
+ case "plugin.goals":
+ case "profile.reports":
+ case "project.reports":
+ case "reportSet.configuration":
+ domDepth++;
+
+ originalHandler = getContentHandler();
+
+ ContentHandler outputContentHandler = getContentHandler();
+ while ( outputContentHandler instanceof XMLFilter )
+ {
+ outputContentHandler = ( (XMLFilter) outputContentHandler ).getContentHandler();
+ }
+ setContentHandler( outputContentHandler );
+ break;
+ default:
+ break;
+ }
+ state.push( localName );
+ }
+ }
+
+ @Override
+ public void endElement( String uri, String localName, String qName )
+ throws SAXException
+ {
+ if ( domDepth > 0 )
+ {
+ domDepth--;
+
+ if ( domDepth == 0 )
+ {
+ setContentHandler( originalHandler );
+ }
+ }
+ else
+ {
+ state.pop();
+ }
+ super.endElement( uri, localName, qName );
+ }
+
+
+}
\ No newline at end of file
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ModulesXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ModulesXMLFilter.java
new file mode 100644
index 0000000..261c853
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ModulesXMLFilter.java
@@ -0,0 +1,111 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Remove all modules, this is just buildtime information
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+class ModulesXMLFilter
+ extends AbstractEventXMLFilter
+{
+ private boolean parsingModules;
+
+ private String state;
+
+ ModulesXMLFilter()
+ {
+ super();
+ }
+
+ <T extends XMLReader & LexicalHandler> ModulesXMLFilter( T parent )
+ {
+ super( parent );
+ }
+
+ @Override
+ public void startElement( String uri, String localName, String qName, Attributes atts )
+ throws SAXException
+ {
+ if ( !parsingModules && "modules".equals( localName ) )
+ {
+ parsingModules = true;
+ }
+
+ if ( parsingModules )
+ {
+ state = localName;
+ }
+
+ super.startElement( uri, localName, qName, atts );
+ }
+
+ @Override
+ public void endElement( String uri, String localName, String qName )
+ throws SAXException
+ {
+ if ( parsingModules )
+ {
+ switch ( localName )
+ {
+ case "modules":
+ executeEvents();
+
+ parsingModules = false;
+ break;
+ default:
+ super.endElement( uri, localName, qName );
+ break;
+ }
+ }
+ else
+ {
+ super.endElement( uri, localName, qName );
+ }
+
+ // for this simple structure resetting to modules it sufficient
+ state = "modules";
+ }
+
+ @Override
+ protected boolean isParsing()
+ {
+ return parsingModules;
+ }
+
+ @Override
+ protected String getState()
+ {
+ return state;
+ }
+
+ @Override
+ protected boolean acceptEvent( String state )
+ {
+ return false;
+ }
+}
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
new file mode 100644
index 0000000..df43ec1
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ParentXMLFilter.java
@@ -0,0 +1,210 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.nio.file.Paths;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.apache.maven.xml.sax.SAXEventUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * <p>
+ * Transforms relativePath to version.
+ * We could decide to simply allow {@code <parent/>}, but let's require the GA for now for checking
+ * This filter does NOT remove the relativePath (which is done by {@link RelativePathXMLFilter}, it will only
+ * optionally include the version based on the path
+ * </p>
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+class ParentXMLFilter
+ extends AbstractEventXMLFilter
+{
+ private boolean parsingParent;
+
+ // states
+ private String state;
+
+ // whiteSpace after <parent>, to be used to position <version>
+ private String parentWhitespace = "";
+
+ private String groupId;
+
+ private String artifactId;
+
+ private String relativePath;
+
+ private boolean hasVersion;
+
+ private Optional<RelativeProject> resolvedParent;
+
+ private final Function<Path, Optional<RelativeProject>> relativePathMapper;
+
+ private Path projectPath;
+
+ /**
+ *
+ *
+ * @param relativePathMapper
+ */
+ ParentXMLFilter( Function<Path, Optional<RelativeProject>> relativePathMapper )
+ {
+ this.relativePathMapper = relativePathMapper;
+ }
+
+ public void setProjectPath( Path projectPath )
+ {
+ this.projectPath = projectPath;
+ }
+
+ @Override
+ protected boolean isParsing()
+ {
+ return parsingParent;
+ }
+
+ @Override
+ protected String getState()
+ {
+ return state;
+ }
+
+ @Override
+ public void startElement( String uri, final String localName, String qName, Attributes atts )
+ throws SAXException
+ {
+ if ( !parsingParent && "parent".equals( localName ) )
+ {
+ parsingParent = true;
+ }
+
+ if ( parsingParent )
+ {
+ state = localName;
+
+ hasVersion |= "version".equals( localName );
+ }
+
+ super.startElement( uri, localName, qName, atts );
+ }
+
+ @Override
+ public void characters( char[] ch, int start, int length )
+ throws SAXException
+ {
+ if ( parsingParent )
+ {
+ final String eventState = state;
+
+ switch ( eventState )
+ {
+ case "parent":
+ parentWhitespace = new String( ch, start, length );
+ break;
+ case "relativePath":
+ relativePath = new String( ch, start, length );
+ break;
+ case "groupId":
+ groupId = new String( ch, start, length );
+ break;
+ case "artifactId":
+ artifactId = new String( ch, start, length );
+ break;
+ default:
+ break;
+ }
+ }
+
+ super.characters( ch, start, length );
+ }
+
+ @Override
+ public void endElement( String uri, final String localName, String qName )
+ throws SAXException
+ {
+ if ( parsingParent )
+ {
+ switch ( localName )
+ {
+ case "parent":
+ if ( !hasVersion || relativePath != null )
+ {
+ resolvedParent =
+ resolveRelativePath( Paths.get( Objects.toString( relativePath, "../pom.xml" ) ) );
+ }
+
+ if ( !hasVersion && resolvedParent.isPresent() )
+ {
+ try ( Includer i = super.include() )
+ {
+ super.characters( parentWhitespace.toCharArray(), 0,
+ parentWhitespace.length() );
+
+ String versionQName = SAXEventUtils.renameQName( qName, "version" );
+
+ super.startElement( uri, "version", versionQName, null );
+
+ String resolvedParentVersion = resolvedParent.get().getVersion();
+
+ super.characters( resolvedParentVersion.toCharArray(), 0,
+ resolvedParentVersion.length() );
+
+ super.endElement( uri, "version", versionQName );
+ }
+ }
+ super.executeEvents();
+
+ parsingParent = false;
+ break;
+ default:
+ // marker?
+ break;
+ }
+ }
+
+ super.endElement( uri, localName, qName );
+ state = "";
+ }
+
+ protected Optional<RelativeProject> resolveRelativePath( Path relativePath )
+ {
+ Optional<RelativeProject> mappedProject =
+ relativePathMapper.apply( projectPath.resolve( relativePath ).normalize() );
+
+ if ( mappedProject.isPresent() )
+ {
+ RelativeProject project = mappedProject.get();
+
+ if ( Objects.equals( groupId, project.getGroupId() )
+ && Objects.equals( artifactId, project.getArtifactId() ) )
+ {
+ return mappedProject;
+ }
+ }
+ return Optional.empty();
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ReactorDependencyXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ReactorDependencyXMLFilter.java
new file mode 100644
index 0000000..38f8fb8
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/ReactorDependencyXMLFilter.java
@@ -0,0 +1,165 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.util.function.BiFunction;
+
+import org.apache.maven.xml.sax.SAXEventUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * Will apply the version if the dependency is part of the reactor
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class ReactorDependencyXMLFilter extends AbstractEventXMLFilter
+{
+ private boolean parsingDependency;
+
+ // states
+ private String state;
+
+ // whiteSpace after <dependency>, to be used to position <version>
+ private String dependencyWhitespace = "";
+
+ private boolean hasVersion;
+
+ private String groupId;
+
+ private String artifactId;
+
+ private final BiFunction<String, String, String> reactorVersionMapper;
+
+ public ReactorDependencyXMLFilter( BiFunction<String, String, String> reactorVersionMapper )
+ {
+ this.reactorVersionMapper = reactorVersionMapper;
+ }
+
+ @Override
+ public void startElement( String uri, String localName, String qName, Attributes atts )
+ throws SAXException
+ {
+ if ( !parsingDependency && "dependency".equals( localName ) )
+ {
+ parsingDependency = true;
+ }
+
+ if ( parsingDependency )
+ {
+ state = localName;
+
+ hasVersion |= "version".equals( localName );
+ }
+ super.startElement( uri, localName, qName, atts );
+ }
+
+ @Override
+ public void characters( char[] ch, int start, int length )
+ throws SAXException
+ {
+ if ( parsingDependency )
+ {
+ final String eventState = state;
+ switch ( eventState )
+ {
+ case "dependency":
+ dependencyWhitespace = new String( ch, start, length );
+ break;
+ case "groupId":
+ groupId = new String( ch, start, length );
+ break;
+ case "artifactId":
+ artifactId = new String( ch, start, length );
+ break;
+ default:
+ break;
+ }
+ }
+ super.characters( ch, start, length );
+ }
+
+ @Override
+ public void endElement( String uri, final String localName, String qName )
+ throws SAXException
+ {
+ if ( parsingDependency )
+ {
+ switch ( localName )
+ {
+ case "dependency":
+ if ( !hasVersion )
+ {
+ String version = getVersion();
+
+ // dependency is not part of reactor, probably it is managed
+ if ( version != null )
+ {
+ try ( Includer i = super.include() )
+ {
+ super.characters( dependencyWhitespace.toCharArray(), 0,
+ dependencyWhitespace.length() );
+ String versionQName = SAXEventUtils.renameQName( qName, "version" );
+
+ super.startElement( uri, "version", versionQName, null );
+ super.characters( version.toCharArray(), 0, version.length() );
+ super.endElement( uri, "version", versionQName );
+ }
+ }
+ }
+ super.executeEvents();
+
+ parsingDependency = false;
+
+ // reset
+ hasVersion = false;
+ groupId = null;
+ artifactId = null;
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ super.endElement( uri, localName, qName );
+
+ state = "";
+ }
+
+ private String getVersion()
+ {
+ return reactorVersionMapper.apply( groupId, artifactId );
+ }
+
+ @Override
+ protected boolean isParsing()
+ {
+ return parsingDependency;
+ }
+
+ @Override
+ protected String getState()
+ {
+ return state;
+ }
+
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/RelativePathXMLFilter.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/RelativePathXMLFilter.java
new file mode 100644
index 0000000..25f2137
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/RelativePathXMLFilter.java
@@ -0,0 +1,108 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Remove relativePath element, has no value for consumer pom
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+class RelativePathXMLFilter
+ extends AbstractEventXMLFilter
+{
+ private boolean parsingParent;
+
+ private String state;
+
+ RelativePathXMLFilter()
+ {
+ super();
+ }
+
+ <T extends XMLReader & LexicalHandler> RelativePathXMLFilter( T parent )
+ {
+ super( parent );
+ }
+
+ @Override
+ public void startElement( String uri, final String localName, String qName, Attributes atts )
+ throws SAXException
+ {
+ if ( !parsingParent && "parent".equals( localName ) )
+ {
+ parsingParent = true;
+ }
+
+ if ( parsingParent )
+ {
+ state = localName;
+ }
+
+ super.startElement( uri, localName, qName, atts );
+ }
+
+ @Override
+ public void endElement( String uri, String localName, String qName )
+ throws SAXException
+ {
+ if ( parsingParent )
+ {
+ switch ( localName )
+ {
+ case "parent":
+ executeEvents();
+
+ parsingParent = false;
+ break;
+ default:
+ break;
+ }
+ }
+
+ super.endElement( uri, localName, qName );
+
+ // for this simple structure resetting to parent it sufficient
+ state = "parent";
+ }
+
+ @Override
+ protected boolean isParsing()
+ {
+ return parsingParent;
+ }
+
+ @Override
+ protected String getState()
+ {
+ return state;
+ }
+
+ @Override
+ protected boolean acceptEvent( String state )
+ {
+ return !"relativePath".equals( state );
+ }
+}
diff --git a/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/RelativeProject.java b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/RelativeProject.java
new file mode 100644
index 0000000..067e170
--- /dev/null
+++ b/maven-xml/src/main/java/org/apache/maven/xml/sax/filter/RelativeProject.java
@@ -0,0 +1,56 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @author Robert Scholte
+ * @since 3.7.0
+ */
+public class RelativeProject
+{
+ private final String groupId;
+
+ private final String artifactId;
+
+ private final String version;
+
+ public RelativeProject( String groupId, String artifactId, String version )
+ {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public String getArtifactId()
+ {
+ return artifactId;
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+}
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/SAXEventUtilsTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/SAXEventUtilsTest.java
new file mode 100644
index 0000000..02e55dc
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/SAXEventUtilsTest.java
@@ -0,0 +1,43 @@
+package org.apache.maven.xml.sax;
+
+/*
+ * 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 static org.junit.Assert.assertThat;
+
+import org.apache.maven.xml.sax.SAXEventUtils;
+
+import static org.hamcrest.CoreMatchers.is;
+
+import org.junit.Test;
+
+public class SAXEventUtilsTest
+{
+ @Test
+ public void replaceWithNamespace()
+ {
+ assertThat( SAXEventUtils.renameQName( "org:bar", "com" ), is( "org:com" ) );
+ }
+
+ @Test
+ public void replaceWithoutNamespace()
+ {
+ assertThat( SAXEventUtils.renameQName( "bar", "com" ), is( "com" ) );
+ }
+}
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/ext/CommentRenormalizerTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/ext/CommentRenormalizerTest.java
new file mode 100644
index 0000000..b6bc381
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/ext/CommentRenormalizerTest.java
@@ -0,0 +1,84 @@
+package org.apache.maven.xml.sax.ext;
+
+/*
+ * 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.xml.sax.ext.LexicalHandler;
+
+@RunWith( Parameterized.class )
+public class CommentRenormalizerTest
+{
+ private LexicalHandler lexicalHandler;
+
+ private final String lineSeparator;
+
+ @Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { "\n" },
+ { "\r\n" },
+ { "\r" }
+ });
+ }
+
+ public CommentRenormalizerTest( String lineSeparator )
+ {
+ this.lineSeparator = lineSeparator;
+ this.lexicalHandler = mock( LexicalHandler.class );
+ }
+
+ @Test
+ public void singleLine()
+ throws Exception
+ {
+ CommentRenormalizer commentRenormalizer = new CommentRenormalizer( lexicalHandler, lineSeparator );
+
+ char[] ch = "single line".toCharArray();
+
+ commentRenormalizer.comment( ch, 0, ch.length );
+
+ verify( lexicalHandler ).comment( ch, 0, ch.length );
+ }
+
+ @Test
+ public void multiLine()
+ throws Exception
+ {
+ CommentRenormalizer commentRenormalizer = new CommentRenormalizer( lexicalHandler, lineSeparator );
+
+ String text = "I%sam%sthe%sbest%s";
+
+ char[] chIn = String.format( text, "\n", "\n", "\n", "\n" ).toCharArray();
+ char[] chOut = String.format( text, lineSeparator, lineSeparator, lineSeparator, lineSeparator ).toCharArray();
+
+ commentRenormalizer.comment( chIn, 0, chIn.length );
+
+ verify( lexicalHandler ).comment( chOut, 0, chOut.length );
+ }
+}
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/AbstractXMLFilterTests.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/AbstractXMLFilterTests.java
new file mode 100644
index 0000000..4fa3b0d
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/AbstractXMLFilterTests.java
@@ -0,0 +1,119 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.maven.xml.Factories;
+import org.apache.maven.xml.sax.filter.AbstractSAXFilter;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+public abstract class AbstractXMLFilterTests
+{
+ public AbstractXMLFilterTests()
+ {
+ super();
+ }
+
+ protected abstract AbstractSAXFilter getFilter() throws TransformerException, SAXException, ParserConfigurationException;
+
+ private void setParent( AbstractSAXFilter filter ) throws SAXException, ParserConfigurationException
+ {
+ if( filter.getParent() == null )
+ {
+ XMLReader r = Factories.newXMLReader();
+
+ filter.setParent( r );
+ filter.setFeature( "http://xml.org/sax/features/namespaces", true );
+ }
+ }
+
+ protected String omitXmlDeclaration() {
+ return "yes";
+ }
+
+ protected String indentAmount() {
+ return null;
+ }
+
+ protected String transform( String input )
+ throws TransformerException, SAXException, ParserConfigurationException
+ {
+ return transform( new StringReader( input ) );
+ }
+
+ protected String transform( Reader input ) throws TransformerException, SAXException, ParserConfigurationException
+ {
+ AbstractSAXFilter filter = getFilter();
+ setParent( filter );
+
+ return transform( input, filter );
+ }
+
+ protected String transform( String input, AbstractSAXFilter filter )
+ throws TransformerException, SAXException, ParserConfigurationException
+ {
+ setParent( filter );
+ return transform( new StringReader( input ), filter );
+ }
+
+ protected String transform( Reader input, AbstractSAXFilter filter )
+ throws TransformerException, SAXException, ParserConfigurationException
+ {
+ Writer writer = new StringWriter();
+ StreamResult result = new StreamResult( writer );
+
+ SAXTransformerFactory transformerFactory = (SAXTransformerFactory) Factories.newTransformerFactory();
+ TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
+ filter.setLexicalHandler( transformerHandler );
+ transformerHandler.getTransformer().setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration() );
+ if ( indentAmount() != null )
+ {
+ transformerHandler.getTransformer().setOutputProperty( OutputKeys.INDENT, "yes" );
+ transformerHandler.getTransformer().setOutputProperty( "{http://xml.apache.org/xslt}indent-amount",
+ indentAmount() );
+ }
+ transformerHandler.setResult( result );
+ Transformer transformer = transformerFactory.newTransformer();
+
+ SAXSource transformSource = new SAXSource( filter, new InputSource( input ) );
+
+ SAXResult transformResult = new SAXResult( transformerHandler );
+ transformResult.setLexicalHandler( filter );
+ transformer.transform( transformSource, transformResult );
+
+ return writer.toString();
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..16d458b
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ConsumerPomXMLFilterTest.java
@@ -0,0 +1,235 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 static org.xmlunit.assertj.XmlAssert.assertThat;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
+{
+ @Override
+ protected String omitXmlDeclaration()
+ {
+ return "no";
+ }
+
+ @Override
+ protected AbstractSAXFilter getFilter() throws SAXException, ParserConfigurationException, TransformerConfigurationException
+ {
+ final BuildPomXMLFilterFactory buildPomXMLFilterFactory = new BuildPomXMLFilterFactory()
+ {
+ @Override
+ protected Function<Path, Optional<RelativeProject>> getRelativePathMapper()
+ {
+ return null;
+ }
+
+ @Override
+ protected BiFunction<String, String, String> getDependencyKeyToVersionMapper()
+ {
+ return null;
+ }
+ };
+
+ ConsumerPomXMLFilter filter = new ConsumerPomXMLFilterFactory( buildPomXMLFilterFactory )
+ {
+ @Override
+ protected Optional<String> getSha1()
+ {
+ return Optional.empty();
+ }
+
+ @Override
+ protected Optional<String> getRevision()
+ {
+ return Optional.empty();
+ }
+
+ @Override
+ protected Optional<String> getChangelist()
+ {
+ return Optional.of( "CL" );
+ }
+ }.get( Paths.get( "pom.xml" ) );
+ filter.setFeature( "http://xml.org/sax/features/namespaces", true );
+ return filter;
+ }
+
+ @Test
+ public void aggregatorWithParent() throws Exception {
+ String input = "<project>\n"
+ + " <parent>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>PARENT</artifactId>\n"
+ + " <version>VERSION</version>\n"
+ + " <relativePath>../pom.xml</relativePath>\n"
+ + " </parent>\n"
+ + " <artifactId>PROJECT</artifactId>\n"
+ + " <modules>\n"
+ + " <module>ab</module>\n"
+ + " <module>../cd</module>\n"
+ + " </modules>\n"
+ + "</project>";
+ String expected = "<project>\n"
+ + " <parent>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>PARENT</artifactId>\n"
+ + " <version>VERSION</version>\n"
+ + " </parent>\n"
+ + " <artifactId>PROJECT</artifactId>\n"
+ + "</project>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).ignoreWhitespace().areIdentical();
+ }
+
+ @Test
+ public void aggregatorWithCliFriendlyVersion() throws Exception {
+ String input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n" +
+ " http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+ " <modelVersion>4.0.0</modelVersion>\n" +
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n" +
+ " <artifactId>parent</artifactId>\n" +
+ " <version>0.9-${changelist}-SNAPSHOT</version>\n" +
+ " <packaging>pom</packaging>\n" +
+ " <name>Multi-Spring Chapter Parent Project</name>\n" +
+ " <modules>\n" +
+ " <module>simple-parent</module>\n" +
+ " </modules>\n" +
+ " \n" +
+ " <pluginRepositories>\n" +
+ " <pluginRepository>\n" +
+ " <id>apache.snapshots</id>\n" +
+ " <url>http://repository.apache.org/snapshots/</url>\n" +
+ " </pluginRepository>\n" +
+ " </pluginRepositories>\n" +
+ "</project>";
+ String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n" +
+ " http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+ " <modelVersion>4.0.0</modelVersion>\n" +
+ " <groupId>org.sonatype.mavenbook.multispring</groupId>\n" +
+ " <artifactId>parent</artifactId>\n" +
+ " <version>0.9-CL-SNAPSHOT</version>\n" +
+ " <packaging>pom</packaging>\n" +
+ " <name>Multi-Spring Chapter Parent Project</name>\n" +
+ " \n" +
+ " <pluginRepositories>\n" +
+ " <pluginRepository>\n" +
+ " <id>apache.snapshots</id>\n" +
+ " <url>http://repository.apache.org/snapshots/</url>\n" +
+ " </pluginRepository>\n" +
+ " </pluginRepositories>\n" +
+ "</project>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).ignoreWhitespace().areIdentical();
+ }
+
+ @Test
+ public void licenseHeader() throws Exception {
+ String input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "\n" +
+ "<!--\n" +
+ "Licensed to the Apache Software Foundation (ASF) under one\n" +
+ "or more contributor license agreements. See the NOTICE file\n" +
+ "distributed with this work for additional information\n" +
+ "regarding copyright ownership. The ASF licenses this file\n" +
+ "to you under the Apache License, Version 2.0 (the\n" +
+ "\"License\"); you may not use this file except in compliance\n" +
+ "with the License. You may obtain a copy of the License at\n" +
+ "\n" +
+ " http://www.apache.org/licenses/LICENSE-2.0\n" +
+ "\n" +
+ "Unless required by applicable law or agreed to in writing,\n" +
+ "software distributed under the License is distributed on an\n" +
+ "\"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n" +
+ "KIND, either express or implied. See the License for the\n" +
+ "specific language governing permissions and limitations\n" +
+ "under the License.\n" +
+ "-->\n" +
+ "\n" +
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+ " <modelVersion>4.0.0</modelVersion>\n" +
+ " <parent>\n" +
+ " <groupId>org.apache.maven</groupId>\n" +
+ " <artifactId>maven</artifactId>\n" +
+ " <version>3.7.0-SNAPSHOT</version>\n" +
+ " </parent>\n" +
+ " <artifactId>maven-xml</artifactId>\n" +
+ " <name>Maven XML</name>\n" +
+ " \n" +
+ " <properties>\n" +
+ " <maven.compiler.source>1.8</maven.compiler.source>\n" +
+ " <maven.compiler.target>1.8</maven.compiler.target>\n" +
+ " </properties>\n" +
+ "\n" +
+ " <build>\n" +
+ " <plugins>\n" +
+ " <plugin>\n" +
+ " <groupId>org.codehaus.mojo</groupId>\n" +
+ " <artifactId>animal-sniffer-maven-plugin</artifactId>\n" +
+ " <configuration>\n" +
+ " <signature>\n" +
+ " <groupId>org.codehaus.mojo.signature</groupId>\n" +
+ " <artifactId>java18</artifactId>\n" +
+ " <version>1.0</version>\n" +
+ " </signature>\n" +
+ " </configuration>\n" +
+ " </plugin>\n" +
+ " </plugins>\n" +
+ " </build>\n" +
+ " \n" +
+ " <dependencies>\n" +
+ " <dependency>\n" +
+ " <groupId>javax.inject</groupId>\n" +
+ " <artifactId>javax.inject</artifactId>\n" +
+ " <optional>true</optional>\n" +
+ " </dependency>\n" +
+ " <dependency>\n" +
+ " <groupId>org.xmlunit</groupId>\n" +
+ " <artifactId>xmlunit-assertj</artifactId>\n" +
+ " <scope>test</scope>\n" +
+ " </dependency>\n" +
+ " </dependencies>\n" +
+ "</project>";
+ String expected = input;
+
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+}
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ModulesXMLFilterTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ModulesXMLFilterTest.java
new file mode 100644
index 0000000..552b721
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ModulesXMLFilterTest.java
@@ -0,0 +1,95 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 static org.xmlunit.assertj.XmlAssert.assertThat;
+
+import org.apache.maven.xml.sax.filter.ModulesXMLFilter;
+import org.junit.Test;
+
+public class ModulesXMLFilterTest extends AbstractXMLFilterTests {
+
+ @Override
+ protected ModulesXMLFilter getFilter()
+ {
+ return new ModulesXMLFilter();
+ }
+
+ @Test
+ public void emptyModules() throws Exception {
+ String input = "<project><modules/></project>";
+ String expected = "<project/>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+ @Test
+ public void setOfModules() throws Exception {
+ String input = "<project><modules>"
+ + "<module>ab</module>"
+ + "<module>../cd</module>"
+ + "</modules></project>";
+ String expected = "<project/>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+ @Test
+ public void noModules() throws Exception {
+ String input = "<project><name>NAME</name></project>";
+ String expected = input;
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+ @Test
+ public void comment() throws Exception {
+
+ String input = "<project><!--before--><modules>"
+ + "<!--pre-in-->"
+ + "<module><!--in-->ab</module>"
+ + "<module>../cd</module>"
+ + "<!--post-in-->"
+ + "</modules>"
+ + "<!--after--></project>";
+ String expected = "<project><!--before--><!--after--></project>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+ @Test
+ public void setOfModulesLF() throws Exception {
+ String input = "<project>\n"
+ + "\n"
+ + " <modules>\n"
+ + " <module>ab</module>\n"
+ + " <module>../cd</module>\n"
+ + " </modules>\n"
+ + "\n"
+ + "</project>\n";
+ String expected = "<project>\n"
+ + "\n"
+ + " \n"
+ + "\n"
+ + "</project>\n";
+ 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
new file mode 100644
index 0000000..bb00222
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ParentXMLFilterTest.java
@@ -0,0 +1,215 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import org.apache.maven.xml.sax.filter.ParentXMLFilter;
+import org.apache.maven.xml.sax.filter.RelativeProject;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+public class ParentXMLFilterTest extends AbstractXMLFilterTests
+{
+ @Override
+ protected ParentXMLFilter getFilter()
+ throws TransformerException, SAXException, ParserConfigurationException
+ {
+ ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.of( new RelativeProject( "GROUPID",
+ "ARTIFACTID",
+ "1.0.0" ) ) );
+ filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
+
+ return filter;
+ }
+
+ @Test
+ public void testMinimum() throws Exception
+ {
+ String input = "<parent/>";
+ String expected = input;
+ String actual = transform( input );
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void testNoRelativePath() throws Exception
+ {
+ String input = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<version>VERSION</version>"
+ + "</parent>";
+ String expected = input;
+
+ String actual = transform( input );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void testDefaultRelativePath() throws Exception
+ {
+ String input = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "</parent>";
+ String expected = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<version>1.0.0</version>"
+ + "</parent>";
+
+ String actual = transform( input );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void testNoVersion() throws Exception
+ {
+ String input = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<relativePath>RELATIVEPATH</relativePath>"
+ + "</parent>";
+ String expected = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<relativePath>RELATIVEPATH</relativePath>"
+ + "<version>1.0.0</version>"
+ + "</parent>";
+
+ String actual = transform( input );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void testInvalidRelativePath() throws Exception
+ {
+ ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.ofNullable( null ) );
+ filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
+
+ String input = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<relativePath>RELATIVEPATH</relativePath>"
+ + "</parent>";
+ String expected = input;
+
+ String actual = transform( input, filter );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void testRelativePathAndVersion() throws Exception
+ {
+ String input = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<relativePath>RELATIVEPATH</relativePath>"
+ + "<version>1.0.0</version>"
+ + "</parent>";
+ String expected = "<parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<relativePath>RELATIVEPATH</relativePath>"
+ + "<version>1.0.0</version>"
+ + "</parent>";
+
+ String actual = transform( input );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void testWithWeirdNamespace() throws Exception
+ {
+ String input = "<relativePath:parent xmlns:relativePath=\"relativePath\">"
+ + "<relativePath:groupId>GROUPID</relativePath:groupId>"
+ + "<relativePath:artifactId>ARTIFACTID</relativePath:artifactId>"
+ + "<relativePath:relativePath>RELATIVEPATH</relativePath:relativePath>"
+ + "</relativePath:parent>";
+ String expected = "<relativePath:parent xmlns:relativePath=\"relativePath\">"
+ + "<relativePath:groupId>GROUPID</relativePath:groupId>"
+ + "<relativePath:artifactId>ARTIFACTID</relativePath:artifactId>"
+ + "<relativePath:relativePath>RELATIVEPATH</relativePath:relativePath>"
+ + "<relativePath:version>1.0.0</relativePath:version>"
+ + "</relativePath:parent>";
+
+ String actual = transform( input );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void comment() throws Exception
+ {
+ String input = "<project><!--before--><parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<!--version here-->"
+ + "</parent>"
+ + "</project>";
+ String expected = "<project><!--before--><parent>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<!--version here-->"
+ + "<version>1.0.0</version>"
+ + "</parent>"
+ + "</project>";
+
+ String actual = transform( input );
+
+ assertEquals( expected, actual );
+ }
+
+ @Test
+ public void testIndent() throws Exception
+ {
+ String input = "<project>\n"
+ + " <parent>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>ARTIFACTID</artifactId>\n"
+ + " <!--version here-->\n"
+ + " </parent>\n"
+ + "</project>";
+ String expected = "<project>" + System.lineSeparator()
+ + " <parent>" + System.lineSeparator()
+ + " <groupId>GROUPID</groupId>" + System.lineSeparator()
+ + " <artifactId>ARTIFACTID</artifactId>" + System.lineSeparator()
+ + " <!--version here-->" + System.lineSeparator()
+ + " <version>1.0.0</version>" + System.lineSeparator()
+ + " </parent>" + System.lineSeparator()
+ + "</project>";
+
+ String actual = transform( input );
+
+ assertEquals( expected, actual );
+ }
+}
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ReactorDependencyXMLFilterTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ReactorDependencyXMLFilterTest.java
new file mode 100644
index 0000000..db6606c
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/ReactorDependencyXMLFilterTest.java
@@ -0,0 +1,145 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 static org.xmlunit.assertj.XmlAssert.assertThat;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+public class ReactorDependencyXMLFilterTest extends AbstractXMLFilterTests
+{
+ @Override
+ protected ReactorDependencyXMLFilter getFilter()
+ throws TransformerException, SAXException, ParserConfigurationException
+ {
+ return new ReactorDependencyXMLFilter( (g, a) -> "1.0.0" );
+ }
+
+ @Test
+ public void testDefaultDependency() throws Exception
+ {
+ String input = "<dependency>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<version>VERSION</version>"
+ + "</dependency>";
+ String expected = input;
+
+ String actual = transform( input );
+
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ public void testManagedDependency() throws Exception
+ {
+ ReactorDependencyXMLFilter filter = new ReactorDependencyXMLFilter( (g, a) -> null );
+
+ String input = "<dependency>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "</dependency>";
+ String expected = input;
+
+ String actual = transform( input, filter );
+
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ public void testReactorDependency() throws Exception
+ {
+ String input = "<dependency>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "</dependency>";
+ String expected = "<dependency>"
+ + "<groupId>GROUPID</groupId>"
+ + "<artifactId>ARTIFACTID</artifactId>"
+ + "<version>1.0.0</version>"
+ + "</dependency>";
+
+ String actual = transform( input );
+
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ public void testReactorDependencyLF() throws Exception
+ {
+ String input = "<dependency>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>ARTIFACTID</artifactId>\n"
+ + " <!-- include version here --> "
+ + "</dependency>";
+ String expected = "<dependency>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>ARTIFACTID</artifactId>\n"
+ + " <!-- include version here -->\n"
+ + " <version>1.0.0</version>\n"
+ + "</dependency>";
+
+ String actual = transform( input );
+
+ assertThat( actual ).and( expected ).ignoreWhitespace().areIdentical();
+ }
+
+ @Test
+ public void multipleDependencies() throws Exception {
+ String input = "<project>\n" +
+ " <modelVersion>4.0.0</modelVersion>\n" +
+ " <groupId>tests.project</groupId>\n" +
+ " <artifactId>duplicate-plugin-defs-merged</artifactId>\n" +
+ " <version>1</version>\n" +
+ " <build>\n" +
+ " <plugins>\n" +
+ " <plugin>\n" +
+ " <artifactId>maven-compiler-plugin</artifactId>\n" +
+ " <dependencies>\n" +
+ " <dependency>\n" +
+ " <groupId>group</groupId>\n" +
+ " <artifactId>first</artifactId>\n" +
+ " <version>1</version>\n" +
+ " </dependency>\n" +
+ " </dependencies>\n" +
+ " </plugin>\n" +
+ " <plugin>\n" +
+ " <artifactId>maven-compiler-plugin</artifactId>\n" +
+ " <dependencies>\n" +
+ " <dependency>\n" +
+ " <groupId>group</groupId>\n" +
+ " <artifactId>second</artifactId>\n" +
+ " <version>1</version>\n" +
+ " </dependency>\n" +
+ " </dependencies>\n" +
+ " </plugin>\n" +
+ " </plugins>\n" +
+ " </build>\n" +
+ "</project>";
+ String expected = input;
+
+ String actual = transform( input );
+
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+}
diff --git a/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/RelativePathXMLFilterTest.java b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/RelativePathXMLFilterTest.java
new file mode 100644
index 0000000..00655b3
--- /dev/null
+++ b/maven-xml/src/test/java/org/apache/maven/xml/sax/filter/RelativePathXMLFilterTest.java
@@ -0,0 +1,115 @@
+package org.apache.maven.xml.sax.filter;
+
+/*
+ * 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 static org.xmlunit.assertj.XmlAssert.assertThat;
+
+import org.apache.maven.xml.sax.filter.RelativePathXMLFilter;
+import org.junit.Test;
+
+public class RelativePathXMLFilterTest extends AbstractXMLFilterTests
+{
+ @Override
+ protected RelativePathXMLFilter getFilter()
+ {
+ return new RelativePathXMLFilter();
+ }
+
+ @Test
+ public void testRelativePath() throws Exception
+ {
+ String input = "<project>\n"
+ + " <parent>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>PARENT</artifactId>\n"
+ + " <version>VERSION</version>\n"
+ + " <relativePath>../pom.xml</relativePath>\n"
+ + " </parent>\n"
+ + " <artifactId>PROJECT</artifactId>\n"
+ + "</project>";
+ String expected = "<project>\n"
+ + " <parent>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>PARENT</artifactId>\n"
+ + " <version>VERSION</version>\n"
+ + " </parent>\n"
+ + " <artifactId>PROJECT</artifactId>\n"
+ + "</project>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+ @Test
+ public void testRelativePathNS() throws Exception
+ {
+ String input = "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ + " <parent>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>PARENT</artifactId>\n"
+ + " <version>VERSION</version>\n"
+ + " <relativePath>../pom.xml</relativePath>\n"
+ + " </parent>\n"
+ + " <artifactId>PROJECT</artifactId>\n"
+ + "</project>";
+ String expected = "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ + " <parent>\n"
+ + " <groupId>GROUPID</groupId>\n"
+ + " <artifactId>PARENT</artifactId>\n"
+ + " <version>VERSION</version>\n"
+ + " </parent>\n"
+ + " <artifactId>PROJECT</artifactId>\n"
+ + "</project>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+ @Test
+ public void testRelativePathPasNS() throws Exception
+ {
+ String input = "<p:project xmlns:p=\"http://maven.apache.org/POM/4.0.0\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ + " <p:parent>\n"
+ + " <p:groupId>GROUPID</p:groupId>\n"
+ + " <p:artifactId>PARENT</p:artifactId>\n"
+ + " <p:version>VERSION</p:version>\n"
+ + " <p:relativePath>../pom.xml</p:relativePath>\n"
+ + " </p:parent>\n"
+ + " <p:artifactId>PROJECT</p:artifactId>\n"
+ + "</p:project>";
+ String expected = "<p:project xmlns:p=\"http://maven.apache.org/POM/4.0.0\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n"
+ + " <p:parent>\n"
+ + " <p:groupId>GROUPID</p:groupId>\n"
+ + " <p:artifactId>PARENT</p:artifactId>\n"
+ + " <p:version>VERSION</p:version>\n"
+ + " </p:parent>\n"
+ + " <p:artifactId>PROJECT</p:artifactId>\n"
+ + "</p:project>";
+ String actual = transform( input );
+ assertThat( actual ).and( expected ).areIdentical();
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index e6dd009..a6c1bb5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,7 @@ under the License.
<module>apache-maven</module> <!-- rename to apache-maven/maven.pom after RAT-268 -->
<module>maven-wrapper</module>
<module>apache-maven/maven-wrapper.pom</module>
+ <module>maven-xml</module>
</modules>
<scm>
@@ -250,6 +251,11 @@ under the License.
<artifactId>maven-slf4j-wrapper</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-xml</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<!--bootstrap-end-comment-->
<!-- Plexus -->
<dependency>
@@ -421,6 +427,12 @@ under the License.
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-assertj</artifactId>
+ <version>${xmlunitVersion}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>${xmlunitVersion}</version>
<scope>test</scope>