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