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 try (FileInputStream pomInputStream = new FileInputStream(project.getFile())) { 066 Model model = modelReader.read(pomInputStream, false); 067 maven2Validation(model); 068 } catch (IOException | XmlPullParserException e) { 069 throw new EnforcerRuleError("Unable to retrieve the MavenProject: ", e); 070 } 071 } 072 073 private void maven2Validation(Model model) throws EnforcerRuleException { 074 List<Dependency> dependencies = model.getDependencies(); 075 Map<String, Integer> duplicateDependencies = validateDependencies(dependencies); 076 077 int duplicates = duplicateDependencies.size(); 078 079 StringBuilder summary = new StringBuilder(); 080 messageBuilder(duplicateDependencies, "dependencies.dependency", summary); 081 082 if (model.getDependencyManagement() != null) { 083 List<Dependency> managementDependencies = 084 model.getDependencyManagement().getDependencies(); 085 Map<String, Integer> duplicateManagementDependencies = validateDependencies(managementDependencies); 086 duplicates += duplicateManagementDependencies.size(); 087 088 messageBuilder(duplicateManagementDependencies, "dependencyManagement.dependencies.dependency", summary); 089 } 090 091 List<Profile> profiles = model.getProfiles(); 092 for (Profile profile : profiles) { 093 List<Dependency> profileDependencies = profile.getDependencies(); 094 095 Map<String, Integer> duplicateProfileDependencies = validateDependencies(profileDependencies); 096 097 duplicates += duplicateProfileDependencies.size(); 098 099 messageBuilder( 100 duplicateProfileDependencies, 101 "profiles.profile[" + profile.getId() + "].dependencies.dependency", 102 summary); 103 104 if (profile.getDependencyManagement() != null) { 105 List<Dependency> profileManagementDependencies = 106 profile.getDependencyManagement().getDependencies(); 107 108 Map<String, Integer> duplicateProfileManagementDependencies = 109 validateDependencies(profileManagementDependencies); 110 111 duplicates += duplicateProfileManagementDependencies.size(); 112 113 messageBuilder( 114 duplicateProfileManagementDependencies, 115 "profiles.profile[" + profile.getId() + "].dependencyManagement.dependencies.dependency", 116 summary); 117 } 118 } 119 120 if (summary.length() > 0) { 121 StringBuilder message = new StringBuilder(); 122 message.append("Found ").append(duplicates).append(" duplicate dependency "); 123 message.append(duplicateDependencies.size() == 1 ? "declaration" : "declarations") 124 .append(" in this project:" + System.lineSeparator()); 125 message.append(summary); 126 throw new EnforcerRuleException(message.toString()); 127 } 128 } 129 130 private void messageBuilder(Map<String, Integer> duplicateDependencies, String prefix, StringBuilder message) { 131 if (!duplicateDependencies.isEmpty()) { 132 for (Map.Entry<String, Integer> entry : duplicateDependencies.entrySet()) { 133 message.append(" - ") 134 .append(prefix) 135 .append('[') 136 .append(entry.getKey()) 137 .append("] (") 138 .append(entry.getValue()) 139 .append(" times)" + System.lineSeparator()); 140 } 141 } 142 } 143 144 private Map<String, Integer> validateDependencies(List<Dependency> dependencies) { 145 Map<String, Integer> duplicateDeps = new HashMap<>(); 146 Set<String> deps = new HashSet<>(); 147 for (Dependency dependency : dependencies) { 148 String key = dependency.getManagementKey(); 149 150 if (deps.contains(key)) { 151 int times = 1; 152 if (duplicateDeps.containsKey(key)) { 153 times = duplicateDeps.get(key); 154 } 155 duplicateDeps.put(key, times + 1); 156 } else { 157 deps.add(key); 158 } 159 } 160 return duplicateDeps; 161 } 162}