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.Artifact;
027import org.apache.maven.artifact.factory.ArtifactFactory;
028import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
029import org.apache.maven.artifact.repository.ArtifactRepository;
030import org.apache.maven.artifact.resolver.ArtifactCollector;
031import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
032import org.apache.maven.enforcer.rule.api.EnforcerRule;
033import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
034import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
035import org.apache.maven.plugin.logging.Log;
036import org.apache.maven.plugins.enforcer.utils.DependencyVersionMap;
037import org.apache.maven.project.MavenProject;
038import org.apache.maven.shared.dependency.tree.DependencyNode;
039import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
040import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
041import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
042import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
043import org.codehaus.plexus.i18n.I18N;
044
045/**
046 * @author <a href="mailto:rex@e-hoffman.org">Rex Hoffman</a>
047 */
048public class DependencyConvergence
049    implements EnforcerRule
050{
051
052    private static Log log;
053
054    private static I18N i18n;
055    
056    private boolean uniqueVersions; 
057
058    public void setUniqueVersions( boolean uniqueVersions )
059    {
060        this.uniqueVersions = uniqueVersions;
061    }
062    
063    /**
064     * Uses the {@link EnforcerRuleHelper} to populate the values of the
065     * {@link DependencyTreeBuilder#buildDependencyTree(MavenProject, ArtifactRepository, ArtifactFactory, ArtifactMetadataSource, ArtifactFilter, ArtifactCollector)}
066     * factory method. <br/>
067     * This method simply exists to hide all the ugly lookup that the {@link EnforcerRuleHelper} has to do.
068     * 
069     * @param helper
070     * @return a Dependency Node which is the root of the project's dependency tree
071     * @throws EnforcerRuleException
072     */
073    private DependencyNode getNode( EnforcerRuleHelper helper )
074        throws EnforcerRuleException
075    {
076        try
077        {
078            MavenProject project = (MavenProject) helper.evaluate( "${project}" );
079            DependencyTreeBuilder dependencyTreeBuilder =
080                (DependencyTreeBuilder) helper.getComponent( DependencyTreeBuilder.class );
081            ArtifactRepository repository = (ArtifactRepository) helper.evaluate( "${localRepository}" );
082            ArtifactFactory factory = (ArtifactFactory) helper.getComponent( ArtifactFactory.class );
083            ArtifactMetadataSource metadataSource =
084                (ArtifactMetadataSource) helper.getComponent( ArtifactMetadataSource.class );
085            ArtifactCollector collector = (ArtifactCollector) helper.getComponent( ArtifactCollector.class );
086            ArtifactFilter filter = null; // we need to evaluate all scopes
087            DependencyNode node =
088                dependencyTreeBuilder.buildDependencyTree( project, repository, factory, metadataSource, filter,
089                                                           collector );
090            return node;
091        }
092        catch ( ExpressionEvaluationException e )
093        {
094            throw new EnforcerRuleException( "Unable to lookup an expression " + e.getLocalizedMessage(), e );
095        }
096        catch ( ComponentLookupException e )
097        {
098            throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e );
099        }
100        catch ( DependencyTreeBuilderException e )
101        {
102            throw new EnforcerRuleException( "Could not build dependency tree " + e.getLocalizedMessage(), e );
103        }
104    }
105
106    public void execute( EnforcerRuleHelper helper )
107        throws EnforcerRuleException
108    {
109        if ( log == null )
110        {
111            log = helper.getLog();
112        }
113        try
114        {
115            if ( i18n == null )
116            {
117                i18n = (I18N) helper.getComponent( I18N.class );
118            }
119            DependencyNode node = getNode( helper );
120            DependencyVersionMap visitor = new DependencyVersionMap( log );
121            visitor.setUniqueVersions( uniqueVersions );
122            node.accept( visitor );
123            List<CharSequence> errorMsgs = new ArrayList<CharSequence>();
124            errorMsgs.addAll( getConvergenceErrorMsgs( visitor.getConflictedVersionNumbers() ) );
125            for ( CharSequence errorMsg : errorMsgs )
126            {
127                log.warn( errorMsg );
128            }
129            if ( errorMsgs.size() > 0 )
130            {
131                throw new EnforcerRuleException( "Failed while enforcing releasability the error(s) are " + errorMsgs );
132            }
133        }
134        catch ( ComponentLookupException e )
135        {
136            throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e );
137        }
138        catch ( Exception e )
139        {
140            throw new EnforcerRuleException( e.getLocalizedMessage(), e );
141        }
142    }
143
144    private String getFullArtifactName( Artifact artifact )
145    {
146        return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion();
147    }
148
149    private StringBuilder buildTreeString( DependencyNode node )
150    {
151        List<String> loc = new ArrayList<String>();
152        DependencyNode currentNode = node;
153        while ( currentNode != null )
154        {
155            loc.add( getFullArtifactName( currentNode.getArtifact() ) );
156            currentNode = currentNode.getParent();
157        }
158        Collections.reverse( loc );
159        StringBuilder builder = new StringBuilder();
160        for ( int i = 0; i < loc.size(); i++ )
161        {
162            for ( int j = 0; j < i; j++ )
163            {
164                builder.append( "  " );
165            }
166            builder.append( "+-" + loc.get( i ) );
167            builder.append( "\n" );
168        }
169        return builder;
170    }
171
172    private List<String> getConvergenceErrorMsgs( List<List<DependencyNode>> errors )
173    {
174        List<String> errorMsgs = new ArrayList<String>();
175        for ( List<DependencyNode> nodeList : errors )
176        {
177            errorMsgs.add( buildConvergenceErrorMsg( nodeList ) );
178        }
179        return errorMsgs;
180    }
181
182    private String buildConvergenceErrorMsg( List<DependencyNode> nodeList )
183    {
184        StringBuilder builder = new StringBuilder();
185        builder.append( "\nDependency convergence error for " + getFullArtifactName( nodeList.get( 0 ).getArtifact() )
186            + " paths to dependency are:\n" );
187        if ( nodeList.size() > 0 )
188        {
189            builder.append( buildTreeString( nodeList.get( 0 ) ) );
190        }
191        for ( DependencyNode node : nodeList.subList( 1, nodeList.size() ) )
192        {
193            builder.append( "and\n" );
194            builder.append( buildTreeString( node ) );
195        }
196        return builder.toString();
197    }
198
199    /**
200     * If your rule is cacheable, you must return a unique id when parameters or conditions change that would cause the
201     * result to be different. Multiple cached results are stored based on their id. The easiest way to do this is to
202     * return a hash computed from the values of your parameters. If your rule is not cacheable, then the result here is
203     * not important, you may return anything.
204     */
205    public String getCacheId()
206    {
207        return "";
208    }
209
210    /**
211     * This tells the system if the results are cacheable at all. Keep in mind that during forked builds and other
212     * things, a given rule may be executed more than once for the same project. This means that even things that change
213     * from project to project may still be cacheable in certain instances.
214     */
215    public boolean isCacheable()
216    {
217        return false;
218    }
219
220    /**
221     * If the rule is cacheable and the same id is found in the cache, the stored results are passed to this method to
222     * allow double checking of the results. Most of the time this can be done by generating unique ids, but sometimes
223     * the results of objects returned by the helper need to be queried. You may for example, store certain objects in
224     * your rule and then query them later.
225     * 
226     * @param rule
227     */
228    public boolean isResultValid( EnforcerRule rule )
229    {
230        return false;
231    }
232}