[MNG-6656] Introduce base for build/consumer pom

This commit is contained in:
rfscholte 2020-06-22 21:24:49 +02:00
parent 881274914a
commit bdec668de9
60 changed files with 4850 additions and 156 deletions

View File

@ -60,6 +60,10 @@ under the License.
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-xml</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
@ -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>

View File

@ -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;
}
}

View File

@ -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 );
}
}
}

View File

@ -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(),

View File

@ -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();
}
catch ( ArtifactResolutionException e )
{
throw new UnresolvableModelException( e.getMessage(), groupId, artifactId, version, e );
}
if ( pomFile == null )
{
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();
}
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() );
}

View File

@ -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<CacheKey, File> pomFiles = new HashMap<>();
private final Map<Path, Model> modelsByPath = new HashMap<>();
public File get( String groupId, String artifactId, String version )
/**
* 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 pomFiles.get( new CacheKey( groupId, artifactId, 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 );
}
public void put( String groupId, String artifactId, String version, File pomFile )
/**
* 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 )
{
pomFiles.put( new CacheKey( groupId, artifactId, version ), pomFile );
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;
}
private static final class CacheKey
static class Builder
{
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 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;
}
GAKey that = (GAKey) obj;
CacheKey that = (CacheKey) 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();
}
}
}

View File

@ -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" ) );
}
}

View File

@ -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 );

View File

@ -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() );
}

View File

@ -58,6 +58,11 @@ under the License.
<groupId>org.apache.maven</groupId>
<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>
@ -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>

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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.
*

View File

@ -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.
@ -382,6 +385,19 @@ public class DefaultModelBuildingRequest
return this;
}
@Override
public Model getFileModel()
{
return fileModel;
}
@Override
public ModelBuildingRequest setFileModel( Model fileModel )
{
this.fileModel = fileModel;
return this;
}
@Override
public Model getRawModel()
{
@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -256,6 +256,19 @@ class FilterModelBuildingRequest
return this;
}
@Override
public Model getFileModel()
{
return request.getFileModel();
}
@Override
public ModelBuildingRequest setFileModel( Model fileModel )
{
request.setFileModel( fileModel );
return this;
}
@Override
public Model 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;
}
}

View File

@ -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 );
}

View File

@ -50,7 +50,8 @@ public interface ModelProblem
BASE,
V20,
V30,
V31
V31,
V37
}
/**

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 );
}
}

View File

@ -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

View File

@ -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,

View File

@ -30,10 +30,22 @@ import org.apache.maven.model.building.ModelProblemCollector;
*/
public interface ModelValidator
{
/**
* Checks the specified file model for missing or invalid values. This model is directly created from the POM
* file and has not been subjected to inheritance, interpolation or profile/default injection.
*
* @param model The model to validate, must not be {@code null}.
* @param request The model building request that holds further settings, must not be {@code null}.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
default void validateFileModel( Model model, ModelBuildingRequest request, ModelProblemCollector problems )
{
// do nothing
}
/**
* Checks the specified (raw) model for missing or invalid values. The raw model is directly created from the POM
* file and has not been subjected to inheritance, interpolation or profile/default injection.
* 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}.

View File

@ -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] ) ) );
}
}

View File

@ -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();
}

View File

@ -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" ) );

47
maven-xml/pom.xml Normal file
View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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 );
}
}

View File

@ -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 );
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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 );
}
}
}

View File

@ -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 );
}
}
}

View File

@ -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;
}
}

View File

@ -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 );
}

View File

@ -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 );
}
}
}

View File

@ -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 );
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -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;
}
}

View File

@ -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" ) );
}
}

View File

@ -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 );
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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 );
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

12
pom.xml
View File

@ -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>
@ -419,6 +425,12 @@ under the License.
<version>${mockitoVersion}</version>
<scope>test</scope>
</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>