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