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