All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.enforcer.RequireUpperBoundDeps Maven / Gradle / Ivy

The newest version!
package org.apache.maven.plugins.enforcer;

/*
 * 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.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.i18n.I18N;

/**
 * Rule to enforce that the resolved dependency is also the most recent one of all transitive dependencies.
 * 
 * @author Geoffrey De Smet
 * @since 1.1
 */
public class RequireUpperBoundDeps
    extends AbstractNonCacheableEnforcerRule
{
    private static Log log;

    private static I18N i18n;

    /**
     * @since 1.3
     */
    private boolean uniqueVersions;

    /**
     * Set to {@code true} if timestamped snapshots should be used.
     * 
     * @param uniqueVersions 
     * @since 1.3
     */
    public void setUniqueVersions( boolean uniqueVersions )
    {
        this.uniqueVersions = uniqueVersions;
    }

    /**
     * Uses the {@link EnforcerRuleHelper} to populate the values of the
     * {@link DependencyTreeBuilder#buildDependencyTree(MavenProject, ArtifactRepository, ArtifactFactory, ArtifactMetadataSource, ArtifactFilter, ArtifactCollector)}
     * factory method. 
* This method simply exists to hide all the ugly lookup that the {@link EnforcerRuleHelper} has to do. * * @param helper * @return a Dependency Node which is the root of the project's dependency tree * @throws EnforcerRuleException when the build should fail */ private DependencyNode getNode( EnforcerRuleHelper helper ) throws EnforcerRuleException { try { MavenProject project = (MavenProject) helper.evaluate( "${project}" ); DependencyTreeBuilder dependencyTreeBuilder = (DependencyTreeBuilder) helper.getComponent( DependencyTreeBuilder.class ); ArtifactRepository repository = (ArtifactRepository) helper.evaluate( "${localRepository}" ); ArtifactFactory factory = (ArtifactFactory) helper.getComponent( ArtifactFactory.class ); ArtifactMetadataSource metadataSource = (ArtifactMetadataSource) helper.getComponent( ArtifactMetadataSource.class ); ArtifactCollector collector = (ArtifactCollector) helper.getComponent( ArtifactCollector.class ); ArtifactFilter filter = null; // we need to evaluate all scopes DependencyNode node = dependencyTreeBuilder.buildDependencyTree( project, repository, factory, metadataSource, filter, collector ); return node; } catch ( ExpressionEvaluationException e ) { throw new EnforcerRuleException( "Unable to lookup an expression " + e.getLocalizedMessage(), e ); } catch ( ComponentLookupException e ) { throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e ); } catch ( DependencyTreeBuilderException e ) { throw new EnforcerRuleException( "Could not build dependency tree " + e.getLocalizedMessage(), e ); } } public void execute( EnforcerRuleHelper helper ) throws EnforcerRuleException { if ( log == null ) { log = helper.getLog(); } try { if ( i18n == null ) { i18n = (I18N) helper.getComponent( I18N.class ); } DependencyNode node = getNode( helper ); RequireUpperBoundDepsVisitor visitor = new RequireUpperBoundDepsVisitor(); visitor.setUniqueVersions( uniqueVersions ); node.accept( visitor ); List errorMessages = buildErrorMessages( visitor.getConflicts() ); if ( errorMessages.size() > 0 ) { throw new EnforcerRuleException( "Failed while enforcing RequireUpperBoundDeps. The error(s) are " + errorMessages ); } } catch ( ComponentLookupException e ) { throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e ); } catch ( Exception e ) { throw new EnforcerRuleException( e.getLocalizedMessage(), e ); } } private List buildErrorMessages( List> conflicts ) { List errorMessages = new ArrayList( conflicts.size() ); for ( List conflict : conflicts ) { errorMessages.add( buildErrorMessage( conflict ) ); } return errorMessages; } private String buildErrorMessage( List conflict ) { StringBuilder errorMessage = new StringBuilder(); errorMessage.append( "\nRequire upper bound dependencies error for " + getFullArtifactName( conflict.get( 0 ), false ) + " paths to dependency are:\n" ); if ( conflict.size() > 0 ) { errorMessage.append( buildTreeString( conflict.get( 0 ) ) ); } for ( DependencyNode node : conflict.subList( 1, conflict.size() ) ) { errorMessage.append( "and\n" ); errorMessage.append( buildTreeString( node ) ); } return errorMessage.toString(); } private StringBuilder buildTreeString( DependencyNode node ) { List loc = new ArrayList(); DependencyNode currentNode = node; while ( currentNode != null ) { StringBuilder line = new StringBuilder( getFullArtifactName( currentNode, false ) ); if ( currentNode.getPremanagedVersion() != null ) { line.append( " (managed) <-- " ); line.append( getFullArtifactName( currentNode, true ) ); } loc.add( line.toString() ); currentNode = currentNode.getParent(); } Collections.reverse( loc ); StringBuilder builder = new StringBuilder(); for ( int i = 0; i < loc.size(); i++ ) { for ( int j = 0; j < i; j++ ) { builder.append( " " ); } builder.append( "+-" ).append( loc.get( i ) ); builder.append( "\n" ); } return builder; } private String getFullArtifactName( DependencyNode node, boolean usePremanaged ) { Artifact artifact = node.getArtifact(); String version = node.getPremanagedVersion(); if ( !usePremanaged || version == null ) { version = uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion(); } return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + version; } private static class RequireUpperBoundDepsVisitor implements DependencyNodeVisitor { private boolean uniqueVersions; public void setUniqueVersions( boolean uniqueVersions ) { this.uniqueVersions = uniqueVersions; } private Map> keyToPairsMap = new LinkedHashMap>(); public boolean visit( DependencyNode node ) { DependencyNodeHopCountPair pair = new DependencyNodeHopCountPair( node ); String key = pair.constructKey(); List pairs = keyToPairsMap.get( key ); if ( pairs == null ) { pairs = new ArrayList(); keyToPairsMap.put( key, pairs ); } pairs.add( pair ); Collections.sort( pairs ); return true; } public boolean endVisit( DependencyNode node ) { return true; } public List> getConflicts() { List> output = new ArrayList>(); for ( List pairs : keyToPairsMap.values() ) { if ( containsConflicts( pairs ) ) { List outputSubList = new ArrayList( pairs.size() ); for ( DependencyNodeHopCountPair pair : pairs ) { outputSubList.add( pair.getNode() ); } output.add( outputSubList ); } } return output; } @SuppressWarnings( "unchecked" ) private boolean containsConflicts( List pairs ) { DependencyNodeHopCountPair resolvedPair = pairs.get( 0 ); // search for artifact with lowest hopCount for ( DependencyNodeHopCountPair hopPair : pairs.subList( 1, pairs.size() ) ) { if ( hopPair.getHopCount() < resolvedPair.getHopCount() ) { resolvedPair = hopPair; } } ArtifactVersion resolvedVersion = resolvedPair.extractArtifactVersion( uniqueVersions, false ); for ( DependencyNodeHopCountPair pair : pairs ) { ArtifactVersion version = pair.extractArtifactVersion( uniqueVersions, true ); if ( resolvedVersion.compareTo( version ) < 0 ) { return true; } } return false; } } private static class DependencyNodeHopCountPair implements Comparable { private DependencyNode node; private int hopCount; private DependencyNodeHopCountPair( DependencyNode node ) { this.node = node; countHops(); } private void countHops() { hopCount = 0; DependencyNode parent = node.getParent(); while ( parent != null ) { hopCount++; parent = parent.getParent(); } } private String constructKey() { Artifact artifact = node.getArtifact(); return artifact.getGroupId() + ":" + artifact.getArtifactId(); } public DependencyNode getNode() { return node; } private ArtifactVersion extractArtifactVersion( boolean uniqueVersions, boolean usePremanagedVersion ) { if ( usePremanagedVersion && node.getPremanagedVersion() != null ) { return new DefaultArtifactVersion( node.getPremanagedVersion() ); } Artifact artifact = node.getArtifact(); String version = uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion(); if ( version != null ) { return new DefaultArtifactVersion( version ); } try { return artifact.getSelectedVersion(); } catch ( OverConstrainedVersionException e ) { throw new RuntimeException( "Version ranges problem with " + node.getArtifact(), e ); } } public int getHopCount() { return hopCount; } public int compareTo( DependencyNodeHopCountPair other ) { return Integer.valueOf( hopCount ).compareTo( Integer.valueOf( other.getHopCount() ) ); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy