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.io.FileNotFoundException; 023import java.io.FileReader; 024import java.io.IOException; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 032import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; 033import org.apache.maven.model.Dependency; 034import org.apache.maven.model.Model; 035import org.apache.maven.model.Profile; 036import org.apache.maven.model.io.xpp3.MavenXpp3Reader; 037import org.apache.maven.project.MavenProject; 038import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; 039import org.codehaus.plexus.util.IOUtil; 040import org.codehaus.plexus.util.xml.pull.XmlPullParserException; 041 042/** 043 * Since Maven 3 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique. 044 * Early versions of Maven 3 already warn, this rule can force to break a build for this reason. 045 * 046 * @author Robert Scholte 047 * @since 1.3 048 * 049 */ 050public class BanDuplicatePomDependencyVersions 051 extends AbstractNonCacheableEnforcerRule 052{ 053 054 public void execute( EnforcerRuleHelper helper ) 055 throws EnforcerRuleException 056 { 057 // get the project 058 MavenProject project; 059 try 060 { 061 project = (MavenProject) helper.evaluate( "${project}" ); 062 } 063 catch ( ExpressionEvaluationException eee ) 064 { 065 throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", eee ); 066 } 067 068 069 // re-read model, because M3 uses optimized model 070 MavenXpp3Reader modelReader = new MavenXpp3Reader(); 071 FileReader pomReader = null; 072 Model model; 073 try 074 { 075 pomReader = new FileReader( project.getFile() ); 076 077 model = modelReader.read( pomReader ); 078 } 079 catch ( FileNotFoundException e ) 080 { 081 throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", e ); 082 } 083 catch ( IOException e ) 084 { 085 throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", e ); 086 } 087 catch ( XmlPullParserException e ) 088 { 089 throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", e ); 090 } 091 finally 092 { 093 IOUtil.close( pomReader ); 094 } 095 096 // @todo reuse ModelValidator when possible 097 098// Object modelValidator = null; 099// try 100// { 101// modelValidator = helper.getComponent( "org.apache.maven.model.validation.ModelValidator" ); 102// } 103// catch ( ComponentLookupException e1 ) 104// { 105// // noop 106// } 107 108 109// if( modelValidator == null ) 110// { 111 maven2Validation( helper, model ); 112// } 113// else 114// { 115// } 116 } 117 118 private void maven2Validation( EnforcerRuleHelper helper, Model model ) 119 throws EnforcerRuleException 120 { 121 @SuppressWarnings( "unchecked" ) 122 List<Dependency> dependencies = model.getDependencies(); 123 Map<String, Integer> duplicateDependencies = validateDependencies( dependencies ); 124 125 int duplicates = duplicateDependencies.size(); 126 127 StringBuilder summary = new StringBuilder(); 128 messageBuilder( duplicateDependencies, "dependencies.dependency", summary ); 129 130 if ( model.getDependencyManagement() != null ) 131 { 132 @SuppressWarnings( "unchecked" ) 133 List<Dependency> managementDependencies = model.getDependencies(); 134 Map<String, Integer> duplicateManagementDependencies = validateDependencies( managementDependencies ); 135 duplicates += duplicateManagementDependencies.size(); 136 137 messageBuilder( duplicateManagementDependencies, "dependencyManagement.dependencies.dependency", summary ); 138 } 139 140 141 @SuppressWarnings( "unchecked" ) 142 List<Profile> profiles = model.getProfiles(); 143 for ( Profile profile : profiles ) 144 { 145 @SuppressWarnings( "unchecked" ) 146 List<Dependency> profileDependencies = profile.getDependencies(); 147 148 Map<String, Integer> duplicateProfileDependencies = validateDependencies( profileDependencies ); 149 150 duplicates += duplicateProfileDependencies.size(); 151 152 messageBuilder( duplicateProfileDependencies, "profiles.profile[" + profile.getId() 153 + "].dependencies.dependency", summary ); 154 155 if ( model.getDependencyManagement() != null ) 156 { 157 @SuppressWarnings( "unchecked" ) 158 List<Dependency> profileManagementDependencies = profile.getDependencies(); 159 160 Map<String, Integer> duplicateProfileManagementDependencies = 161 validateDependencies( profileManagementDependencies ); 162 163 duplicates += duplicateProfileManagementDependencies.size(); 164 165 messageBuilder( duplicateProfileManagementDependencies, "profiles.profile[" + profile.getId() 166 + "].dependencyManagement.dependencies.dependency", summary ); 167 } 168 } 169 170 if ( summary.length() > 0 ) 171 { 172 StringBuilder message = new StringBuilder(); 173 message.append( "Found " ).append( duplicates ).append( " duplicate dependency " ); 174 message.append( duplicateDependencies.size() == 1 ? "declaration" : "declarations" ).append( " in this project:\n" ); 175 message.append( summary ); 176 throw new EnforcerRuleException( message.toString() ); 177 } 178 } 179 180 private void messageBuilder( Map<String, Integer> duplicateDependencies, String prefix, StringBuilder message ) 181 { 182 if ( !duplicateDependencies.isEmpty() ) 183 { 184 for ( Map.Entry<String, Integer> entry : duplicateDependencies.entrySet() ) 185 { 186 message.append( " - " ).append( prefix ).append( '[' ).append( entry.getKey() ).append( "] ( " ).append( entry.getValue() ).append( " times )\n" ); 187 } 188 } 189 } 190 191 192 private Map<String, Integer> validateDependencies( List<Dependency> dependencies ) 193 throws EnforcerRuleException 194 { 195 Map<String, Integer> duplicateDeps = new HashMap<String, Integer>(); 196 Set<String> deps = new HashSet<String>(); 197 for ( Dependency dependency : dependencies ) 198 { 199 String key = dependency.getManagementKey(); 200 201 if ( deps.contains( key ) ) 202 { 203 int times = 1; 204 if ( duplicateDeps.containsKey( key ) ) 205 { 206 times = duplicateDeps.get( key ); 207 } 208 duplicateDeps.put( key, times + 1 ); 209 } 210 else 211 { 212 deps.add( key ); 213 } 214 } 215 return duplicateDeps; 216 } 217 218}