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}