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