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. Maven 3.10.0 and Maven 4 reject such POMs 045 * themselves, before this rule is executed. 046 * 047 * @author Robert Scholte 048 * @since 1.3 049 */ 050@Named("banDuplicatePomDependencyVersions") 051public final class BanDuplicatePomDependencyVersions extends AbstractStandardEnforcerRule { 052 053 private final MavenProject project; 054 055 @Inject 056 public BanDuplicatePomDependencyVersions(MavenProject project) { 057 this.project = Objects.requireNonNull(project); 058 } 059 060 @Override 061 public void execute() throws EnforcerRuleException { 062 063 // re-read model, because M3 uses optimized model 064 MavenXpp3Reader modelReader = new MavenXpp3Reader(); 065 066 try (FileInputStream pomInputStream = new FileInputStream(project.getFile())) { 067 Model model = modelReader.read(pomInputStream, false); 068 maven2Validation(model); 069 } catch (IOException | XmlPullParserException e) { 070 throw new EnforcerRuleError("Unable to retrieve the MavenProject: ", e); 071 } 072 } 073 074 private void maven2Validation(Model model) throws EnforcerRuleException { 075 List<Dependency> dependencies = model.getDependencies(); 076 Map<String, Integer> duplicateDependencies = validateDependencies(dependencies); 077 078 int duplicates = duplicateDependencies.size(); 079 080 StringBuilder summary = new StringBuilder(); 081 messageBuilder(duplicateDependencies, "dependencies.dependency", summary); 082 083 if (model.getDependencyManagement() != null) { 084 List<Dependency> managementDependencies = 085 model.getDependencyManagement().getDependencies(); 086 Map<String, Integer> duplicateManagementDependencies = validateDependencies(managementDependencies); 087 duplicates += duplicateManagementDependencies.size(); 088 089 messageBuilder(duplicateManagementDependencies, "dependencyManagement.dependencies.dependency", summary); 090 } 091 092 List<Profile> profiles = model.getProfiles(); 093 for (Profile profile : profiles) { 094 List<Dependency> profileDependencies = profile.getDependencies(); 095 096 Map<String, Integer> duplicateProfileDependencies = validateDependencies(profileDependencies); 097 098 duplicates += duplicateProfileDependencies.size(); 099 100 messageBuilder( 101 duplicateProfileDependencies, 102 "profiles.profile[" + profile.getId() + "].dependencies.dependency", 103 summary); 104 105 if (profile.getDependencyManagement() != null) { 106 List<Dependency> profileManagementDependencies = 107 profile.getDependencyManagement().getDependencies(); 108 109 Map<String, Integer> duplicateProfileManagementDependencies = 110 validateDependencies(profileManagementDependencies); 111 112 duplicates += duplicateProfileManagementDependencies.size(); 113 114 messageBuilder( 115 duplicateProfileManagementDependencies, 116 "profiles.profile[" + profile.getId() + "].dependencyManagement.dependencies.dependency", 117 summary); 118 } 119 } 120 121 if (summary.length() > 0) { 122 StringBuilder message = new StringBuilder(); 123 if (getMessage() != null) { 124 message.append(getMessage()).append("\n"); 125 } 126 message.append("Found ").append(duplicates).append(" duplicate dependency "); 127 message.append(duplicateDependencies.size() == 1 ? "declaration" : "declarations") 128 .append(" in this project:" + System.lineSeparator()); 129 message.append(summary); 130 throw new EnforcerRuleException(message.toString()); 131 } 132 } 133 134 private void messageBuilder(Map<String, Integer> duplicateDependencies, String prefix, StringBuilder message) { 135 if (!duplicateDependencies.isEmpty()) { 136 for (Map.Entry<String, Integer> entry : duplicateDependencies.entrySet()) { 137 message.append(" - ") 138 .append(prefix) 139 .append('[') 140 .append(entry.getKey()) 141 .append("] (") 142 .append(entry.getValue()) 143 .append(" times)" + System.lineSeparator()); 144 } 145 } 146 } 147 148 private Map<String, Integer> validateDependencies(List<Dependency> dependencies) { 149 Map<String, Integer> duplicateDeps = new HashMap<>(); 150 Set<String> deps = new HashSet<>(); 151 for (Dependency dependency : dependencies) { 152 String key = dependency.getManagementKey(); 153 154 if (deps.contains(key)) { 155 int times = 1; 156 if (duplicateDeps.containsKey(key)) { 157 times = duplicateDeps.get(key); 158 } 159 duplicateDeps.put(key, times + 1); 160 } else { 161 deps.add(key); 162 } 163 } 164 return duplicateDeps; 165 } 166}