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.util.ArrayList;
023import java.util.Hashtable;
024import java.util.List;
025
026import org.apache.maven.enforcer.rule.api.EnforcerLevel;
027import org.apache.maven.enforcer.rule.api.EnforcerRule;
028import org.apache.maven.enforcer.rule.api.EnforcerRule2;
029import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
030import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
031import org.apache.maven.execution.MavenSession;
032import org.apache.maven.plugin.AbstractMojo;
033import org.apache.maven.plugin.MojoExecution;
034import org.apache.maven.plugin.MojoExecutionException;
035import org.apache.maven.plugin.logging.Log;
036import org.apache.maven.plugins.annotations.Component;
037import org.apache.maven.plugins.annotations.LifecyclePhase;
038import org.apache.maven.plugins.annotations.Mojo;
039import org.apache.maven.plugins.annotations.Parameter;
040import org.apache.maven.plugins.annotations.ResolutionScope;
041import org.apache.maven.project.MavenProject;
042import org.apache.maven.project.path.PathTranslator;
043import org.codehaus.plexus.PlexusConstants;
044import org.codehaus.plexus.PlexusContainer;
045import org.codehaus.plexus.context.Context;
046import org.codehaus.plexus.context.ContextException;
047import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
048
049/**
050 * This goal executes the defined enforcer-rules once per module.
051 *
052 * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
053 * @version $Id: EnforceMojo.java 1696663 2015-08-19 20:20:14Z khmarbaise $
054 */
055@Mojo( 
056    name = "enforce", 
057    defaultPhase = LifecyclePhase.VALIDATE, 
058    requiresDependencyCollection = ResolutionScope.TEST, 
059    threadSafe = true 
060)
061public class EnforceMojo
062    extends AbstractMojo
063    implements Contextualizable
064{
065    /**
066     * This is a static variable used to persist the cached results across plugin invocations.
067     */
068    protected static Hashtable<String, EnforcerRule> cache = new Hashtable<String, EnforcerRule>();
069
070    /**
071     * Path Translator needed by the ExpressionEvaluator
072     */
073    @Component( role = PathTranslator.class )
074    protected PathTranslator translator;
075
076    /**
077     * MojoExecution needed by the ExpressionEvaluator
078     */
079    @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
080    protected MojoExecution mojoExecution;
081
082    /**
083     * The MavenSession
084     */
085    @Parameter( defaultValue = "${session}", readonly = true, required = true )
086    protected MavenSession session;
087
088    /**
089     * POM
090     */
091    @Parameter( defaultValue = "${project}", readonly = true, required = true )
092    protected MavenProject project;
093
094    /**
095     * Flag to easily skip all checks
096     */
097    @Parameter( property = "enforcer.skip", defaultValue = "false" )
098    protected boolean skip = false;
099
100    /**
101     * Flag to fail the build if a version check fails.
102     */
103    @Parameter( property = "enforcer.fail", defaultValue = "true" )
104    private boolean fail = true;
105
106    /**
107     * Fail on the first rule that doesn't pass
108     */
109    @Parameter( property = "enforcer.failFast", defaultValue = "false" )
110    private boolean failFast = false;
111
112    /**
113     * Array of objects that implement the EnforcerRule interface to execute.
114     */
115    @Parameter( required = true )
116    private EnforcerRule[] rules;
117
118    /**
119     * Use this flag to disable rule result caching. This will cause all rules to execute on each project even if the
120     * rule indicates it can safely be cached.
121     */
122    @Parameter( property = "enforcer.ignoreCache", defaultValue = "false" )
123    protected boolean ignoreCache = false;
124
125    // set by the contextualize method. Only way to get the
126    // plugin's container in 2.0.x
127    protected PlexusContainer container;
128
129    public void contextualize( Context context )
130        throws ContextException
131    {
132        container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
133    }
134
135    /**
136     * Entry point to the mojo
137     * 
138     * @throws MojoExecutionException
139     */
140    public void execute()
141        throws MojoExecutionException
142    {
143        Log log = this.getLog();
144
145        EnforcerExpressionEvaluator evaluator = new EnforcerExpressionEvaluator( session, translator, project, 
146                                                                                 mojoExecution );
147
148        // the entire execution can be easily skipped
149        if ( !skip )
150        {
151            // list to store exceptions
152            List<String> list = new ArrayList<String>();
153
154            // make sure the rules exist
155            if ( rules != null && rules.length > 0 )
156            {
157                String currentRule = "Unknown";
158
159                // create my helper
160                EnforcerRuleHelper helper = new DefaultEnforcementRuleHelper( session, evaluator, log, container );
161
162                // if we are only warning, then disable
163                // failFast
164                if ( !fail )
165                {
166                    failFast = false;
167                }
168
169                boolean hasErrors = false;
170
171                // go through each rule
172                for ( int i = 0; i < rules.length; i++ )
173                {
174
175                    // prevent against empty rules
176                    EnforcerRule rule = rules[i];
177                    final EnforcerLevel level = getLevel( rule );
178                    if ( rule != null )
179                    {
180                        // store the current rule for
181                        // logging purposes
182                        currentRule = rule.getClass().getName();
183                        log.debug( "Executing rule: " + currentRule );
184                        try
185                        {
186                            if ( ignoreCache || shouldExecute( rule ) )
187                            {
188                                // execute the rule
189                                // noinspection
190                                // SynchronizationOnLocalVariableOrMethodParameter
191                                synchronized ( rule )
192                                {
193                                    rule.execute( helper );
194                                }
195                            }
196                        }
197                        catch ( EnforcerRuleException e )
198                        {
199                            // i can throw an exception
200                            // because failfast will be
201                            // false if fail is false.
202                            if ( failFast && level == EnforcerLevel.ERROR )
203                            {
204                                throw new MojoExecutionException( currentRule + " failed with message:\n"
205                                    + e.getMessage(), e );
206                            }
207                            else
208                            {
209                                if ( level == EnforcerLevel.ERROR )
210                                {
211                                    hasErrors = true;
212                                    list.add( "Rule " + i + ": " + currentRule + " failed with message:\n"
213                                        + e.getMessage() );
214                                    log.debug( "Adding failure due to exception", e );
215                                }
216                                else
217                                {
218                                    list.add( "Rule " + i + ": " + currentRule + " warned with message:\n"
219                                        + e.getMessage() );
220                                    log.debug( "Adding warning due to exception", e );
221                                }
222                            }
223                        }
224                    }
225                }
226
227                // if we found anything
228                // CHECKSTYLE_OFF: LineLength
229                if ( !list.isEmpty() )
230                {
231                    for ( String failure : list )
232                    {
233                        log.warn( failure );
234                    }
235                    if ( fail && hasErrors )
236                    {
237                        throw new MojoExecutionException(
238                                                          "Some Enforcer rules have failed. Look above for specific messages explaining why the rule failed." );
239                    }
240                }
241                // CHECKSTYLE_ON: LineLength
242            }
243            else
244            {
245                // CHECKSTYLE_OFF: LineLength
246                throw new MojoExecutionException(
247                                                  "No rules are configured. Use the skip flag if you want to disable execution." );
248                // CHECKSTYLE_ON: LineLength
249            }
250        }
251        else
252        {
253            log.info( "Skipping Rule Enforcement." );
254        }
255    }
256
257    /**
258     * This method determines if a rule should execute based on the cache
259     *
260     * @param rule the rule to verify
261     * @return {@code true} if rule should be executed, otherwise {@code false}
262     */
263    protected boolean shouldExecute( EnforcerRule rule )
264    {
265        if ( rule.isCacheable() )
266        {
267            Log log = this.getLog();
268            log.debug( "Rule " + rule.getClass().getName() + " is cacheable." );
269            String key = rule.getClass().getName() + " " + rule.getCacheId();
270            if ( EnforceMojo.cache.containsKey( key ) )
271            {
272                log.debug( "Key " + key + " was found in the cache" );
273                if ( rule.isResultValid( (EnforcerRule) cache.get( key ) ) )
274                {
275                    log.debug( "The cached results are still valid. Skipping the rule: " + rule.getClass().getName() );
276                    return false;
277                }
278            }
279
280            // add it to the cache of executed rules
281            EnforceMojo.cache.put( key, rule );
282        }
283        return true;
284    }
285
286    /**
287     * @return the fail
288     */
289    public boolean isFail()
290    {
291        return this.fail;
292    }
293
294    /**
295     * @param theFail the fail to set
296     */
297    public void setFail( boolean theFail )
298    {
299        this.fail = theFail;
300    }
301
302    /**
303     * @return the rules
304     */
305    public EnforcerRule[] getRules()
306    {
307        return this.rules;
308    }
309
310    /**
311     * @param theRules the rules to set
312     */
313    public void setRules( EnforcerRule[] theRules )
314    {
315        this.rules = theRules;
316    }
317
318    /**
319     * @param theFailFast the failFast to set
320     */
321    public void setFailFast( boolean theFailFast )
322    {
323        this.failFast = theFailFast;
324    }
325
326    public boolean isFailFast()
327    {
328        return failFast;
329    }
330
331    protected String createRuleMessage( int i, String currentRule, EnforcerRuleException e )
332    {
333        return "Rule " + i + ": " + currentRule + " failed with message:\n" + e.getMessage();
334    }
335
336    /**
337     * @param theTranslator the translator to set
338     */
339    public void setTranslator( PathTranslator theTranslator )
340    {
341        this.translator = theTranslator;
342    }
343
344    /**
345     * Returns the level of the rule, defaults to {@link EnforcerLevel#ERROR} for backwards compatibility.
346     *
347     * @param rule might be of type {@link EnforcerRule2}.
348     * @return level of the rule.
349     */
350    private EnforcerLevel getLevel( EnforcerRule rule )
351    {
352        if ( rule instanceof EnforcerRule2 )
353        {
354            return ( (EnforcerRule2) rule ).getLevel();
355        }
356        else
357        {
358            return EnforcerLevel.ERROR;
359        }
360    }
361
362    /**
363     * @return the skip
364     */
365    public boolean isSkip()
366    {
367        return this.skip;
368    }
369
370    /**
371     * @param theSkip the skip to set
372     */
373    public void setSkip( boolean theSkip )
374    {
375        this.skip = theSkip;
376    }
377
378    /**
379     * @return the project
380     */
381    public MavenProject getProject()
382    {
383        return this.project;
384    }
385
386    /**
387     * @param theProject the project to set
388     */
389    public void setProject( MavenProject theProject )
390    {
391        this.project = theProject;
392    }
393
394    /**
395     * @return the session
396     */
397    public MavenSession getSession()
398    {
399        return this.session;
400    }
401
402    /**
403     * @param theSession the session to set
404     */
405    public void setSession( MavenSession theSession )
406    {
407        this.session = theSession;
408    }
409
410    /**
411     * @return the translator
412     */
413    public PathTranslator getTranslator()
414    {
415        return this.translator;
416    }
417}