001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.enforcer.rules; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023 024import java.io.FileInputStream; 025import java.io.IOException; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Objects; 031import java.util.Set; 032 033import org.apache.maven.enforcer.rule.api.EnforcerRuleError; 034import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 035import org.apache.maven.model.Dependency; 036import org.apache.maven.model.Model; 037import org.apache.maven.model.Profile; 038import org.apache.maven.model.io.xpp3.MavenXpp3Reader; 039import org.apache.maven.project.MavenProject; 040import org.codehaus.plexus.util.xml.pull.XmlPullParserException; 041 042/** 043 * Since Maven 3 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique. Early versions of Maven 044 * 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@Named("banDuplicatePomDependencyVersions") 050public final class BanDuplicatePomDependencyVersions extends AbstractStandardEnforcerRule { 051 052 private final MavenProject project; 053 054 @Inject 055 public BanDuplicatePomDependencyVersions(MavenProject project) { 056 this.project = Objects.requireNonNull(project); 057 } 058 059 @Override 060 public void execute() throws EnforcerRuleException { 061 062 // re-read model, because M3 uses optimized model 063 MavenXpp3Reader modelReader = new MavenXpp3Reader(); 064 065 Model model; 066 try (FileInputStream pomInputStream = new FileInputStream(project.getFile())) { 067 model = modelReader.read(pomInputStream, false); 068 } catch (IOException | XmlPullParserException e) { 069 throw new EnforcerRuleError("Unable to retrieve the MavenProject: ", e); 070 } 071 072 // @todo reuse ModelValidator when possible 073 074 // Object modelValidator = null; 075 // try 076 // { 077 // modelValidator = helper.getComponent( "org.apache.maven.model.validation.ModelValidator" ); 078 // } 079 // catch ( ComponentLookupException e1 ) 080 // { 081 // // noop 082 // } 083 084 // if( modelValidator == null ) 085 // { 086 maven2Validation(model); 087 // } 088 // else 089 // { 090 // } 091 } 092 093 private void maven2Validation(Model model) throws EnforcerRuleException { 094 List<Dependency> dependencies = model.getDependencies(); 095 Map<String, Integer> duplicateDependencies = validateDependencies(dependencies); 096 097 int duplicates = duplicateDependencies.size(); 098 099 StringBuilder summary = new StringBuilder(); 100 messageBuilder(duplicateDependencies, "dependencies.dependency", summary); 101 102 if (model.getDependencyManagement() != null) { 103 List<Dependency> managementDependencies = 104 model.getDependencyManagement().getDependencies(); 105 Map<String, Integer> duplicateManagementDependencies = validateDependencies(managementDependencies); 106 duplicates += duplicateManagementDependencies.size(); 107 108 messageBuilder(duplicateManagementDependencies, "dependencyManagement.dependencies.dependency", summary); 109 } 110 111 List<Profile> profiles = model.getProfiles(); 112 for (Profile profile : profiles) { 113 List<Dependency> profileDependencies = profile.getDependencies(); 114 115 Map<String, Integer> duplicateProfileDependencies = validateDependencies(profileDependencies); 116 117 duplicates += duplicateProfileDependencies.size(); 118 119 messageBuilder( 120 duplicateProfileDependencies, 121 "profiles.profile[" + profile.getId() + "].dependencies.dependency", 122 summary); 123 124 if (profile.getDependencyManagement() != null) { 125 List<Dependency> profileManagementDependencies = 126 profile.getDependencyManagement().getDependencies(); 127 128 Map<String, Integer> duplicateProfileManagementDependencies = 129 validateDependencies(profileManagementDependencies); 130 131 duplicates += duplicateProfileManagementDependencies.size(); 132 133 messageBuilder( 134 duplicateProfileManagementDependencies, 135 "profiles.profile[" + profile.getId() + "].dependencyManagement.dependencies.dependency", 136 summary); 137 } 138 } 139 140 if (summary.length() > 0) { 141 StringBuilder message = new StringBuilder(); 142 message.append("Found ").append(duplicates).append(" duplicate dependency "); 143 message.append(duplicateDependencies.size() == 1 ? "declaration" : "declarations") 144 .append(" in this project:" + System.lineSeparator()); 145 message.append(summary); 146 throw new EnforcerRuleException(message.toString()); 147 } 148 } 149 150 private void messageBuilder(Map<String, Integer> duplicateDependencies, String prefix, StringBuilder message) { 151 if (!duplicateDependencies.isEmpty()) { 152 for (Map.Entry<String, Integer> entry : duplicateDependencies.entrySet()) { 153 message.append(" - ") 154 .append(prefix) 155 .append('[') 156 .append(entry.getKey()) 157 .append("] (") 158 .append(entry.getValue()) 159 .append(" times)" + System.lineSeparator()); 160 } 161 } 162 } 163 164 private Map<String, Integer> validateDependencies(List<Dependency> dependencies) { 165 Map<String, Integer> duplicateDeps = new HashMap<>(); 166 Set<String> deps = new HashSet<>(); 167 for (Dependency dependency : dependencies) { 168 String key = dependency.getManagementKey(); 169 170 if (deps.contains(key)) { 171 int times = 1; 172 if (duplicateDeps.containsKey(key)) { 173 times = duplicateDeps.get(key); 174 } 175 duplicateDeps.put(key, times + 1); 176 } else { 177 deps.add(key); 178 } 179 } 180 return duplicateDeps; 181 } 182}