001package org.apache.maven.plugins.enforcer;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.List;
025
026import org.apache.maven.artifact.repository.ArtifactRepository;
027import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
028import org.apache.maven.enforcer.rule.api.EnforcerRule;
029import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
030import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
031import org.apache.maven.execution.MavenSession;
032import org.apache.maven.plugin.logging.Log;
033import org.apache.maven.plugins.enforcer.utils.DependencyVersionMap;
034import org.apache.maven.project.DefaultProjectBuildingRequest;
035import org.apache.maven.project.MavenProject;
036import org.apache.maven.project.ProjectBuildingRequest;
037import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
038import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
039import org.apache.maven.shared.dependency.graph.DependencyNode;
040import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
041import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
042
043/**
044 * @author <a href="mailto:rex@e-hoffman.org">Rex Hoffman</a>
045 */
046public class DependencyConvergence
047    implements EnforcerRule
048{
049    private static Log log;
050
051    private boolean uniqueVersions;
052
053    public void setUniqueVersions( boolean uniqueVersions )
054    {
055        this.uniqueVersions = uniqueVersions;
056    }
057
058    // CHECKSTYLE_OFF: LineLength
059    /**
060     * Uses the {@link EnforcerRuleHelper} to populate the values of the
061     * {@link DependencyTreeBuilder#buildDependencyTree(MavenProject, ArtifactRepository, ArtifactFactory, ArtifactMetadataSource, ArtifactFilter, ArtifactCollector)}
062     * factory method. <br/>
063     * This method simply exists to hide all the ugly lookup that the {@link EnforcerRuleHelper} has to do.
064     * 
065     * @param helper
066     * @return a Dependency Node which is the root of the project's dependency tree
067     * @throws EnforcerRuleException
068     */
069    // CHECKSTYLE_ON: LineLength
070    private DependencyNode getNode( EnforcerRuleHelper helper )
071        throws EnforcerRuleException
072    {
073        try
074        {
075            MavenProject project = (MavenProject) helper.evaluate( "${project}" );
076            MavenSession session = (MavenSession) helper.evaluate( "${session}" );
077            DependencyCollectorBuilder dependencyCollectorBuilder =
078                helper.getComponent( DependencyCollectorBuilder.class );
079            ArtifactRepository repository = (ArtifactRepository) helper.evaluate( "${localRepository}" );
080
081            ProjectBuildingRequest buildingRequest =
082                new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
083            buildingRequest.setProject( project );
084            buildingRequest.setLocalRepository( repository );
085            ArtifactFilter filter = null; // we need to evaluate all scopes
086
087            return dependencyCollectorBuilder.collectDependencyGraph( buildingRequest, filter );
088        }
089        catch ( ExpressionEvaluationException | ComponentLookupException e )
090        {
091            throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e );
092        }
093        catch ( DependencyCollectorBuilderException e )
094        {
095            throw new EnforcerRuleException( "Could not build dependency tree " + e.getLocalizedMessage(), e );
096        }
097    }
098
099    @Override
100    public void execute( EnforcerRuleHelper helper )
101        throws EnforcerRuleException
102    {
103        if ( log == null )
104        {
105            log = helper.getLog();
106        }
107        try
108        {
109            DependencyNode node = getNode( helper );
110            DependencyVersionMap visitor = new DependencyVersionMap( log );
111            visitor.setUniqueVersions( uniqueVersions );
112            node.accept( visitor );
113            List<CharSequence> errorMsgs = new ArrayList<>();
114            errorMsgs.addAll( getConvergenceErrorMsgs( visitor.getConflictedVersionNumbers() ) );
115            for ( CharSequence errorMsg : errorMsgs )
116            {
117                log.warn( errorMsg );
118            }
119            if ( errorMsgs.size() > 0 )
120            {
121                throw new EnforcerRuleException( "Failed while enforcing releasability. "
122                    + "See above detailed error message." );
123            }
124        }
125        catch ( Exception e )
126        {
127            throw new EnforcerRuleException( e.getLocalizedMessage(), e );
128        }
129    }
130
131    private StringBuilder buildTreeString( DependencyNode node )
132    {
133        List<String> loc = new ArrayList<>();
134        DependencyNode currentNode = node;
135        while ( currentNode != null )
136        {
137            loc.add( currentNode.getArtifact().toString() );
138            currentNode = currentNode.getParent();
139        }
140        Collections.reverse( loc );
141        StringBuilder builder = new StringBuilder();
142        for ( int i = 0; i < loc.size(); i++ )
143        {
144            for ( int j = 0; j < i; j++ )
145            {
146                builder.append( "  " );
147            }
148            builder.append( "+-" + loc.get( i ) );
149            builder.append( System.lineSeparator() );
150        }
151        return builder;
152    }
153
154    private List<String> getConvergenceErrorMsgs( List<List<DependencyNode>> errors )
155    {
156        List<String> errorMsgs = new ArrayList<>();
157        for ( List<DependencyNode> nodeList : errors )
158        {
159            errorMsgs.add( buildConvergenceErrorMsg( nodeList ) );
160        }
161        return errorMsgs;
162    }
163
164    private String buildConvergenceErrorMsg( List<DependencyNode> nodeList )
165    {
166        StringBuilder builder = new StringBuilder();
167        builder.append( System.lineSeparator() + "Dependency convergence error for "
168            + nodeList.get( 0 ).getArtifact().toString()
169            + " paths to dependency are:" + System.lineSeparator() );
170        if ( nodeList.size() > 0 )
171        {
172            builder.append( buildTreeString( nodeList.get( 0 ) ) );
173        }
174        for ( DependencyNode node : nodeList.subList( 1, nodeList.size() ) )
175        {
176            builder.append( "and" + System.lineSeparator() );
177            builder.append( buildTreeString( node ) );
178        }
179        return builder.toString();
180    }
181
182    @Override
183    public String getCacheId()
184    {
185        return "";
186    }
187
188    @Override
189    public boolean isCacheable()
190    {
191        return false;
192    }
193
194    @Override
195    public boolean isResultValid( EnforcerRule rule )
196    {
197        return false;
198    }
199}