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