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