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.EnforcerRule;
027import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
028import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
029import org.apache.maven.execution.MavenSession;
030import org.apache.maven.plugin.AbstractMojo;
031import org.apache.maven.plugin.MojoExecution;
032import org.apache.maven.plugin.MojoExecutionException;
033import org.apache.maven.plugin.logging.Log;
034import org.apache.maven.plugins.annotations.Component;
035import org.apache.maven.plugins.annotations.Parameter;
036import org.apache.maven.project.MavenProject;
037import org.apache.maven.project.path.PathTranslator;
038import org.codehaus.plexus.PlexusConstants;
039import org.codehaus.plexus.PlexusContainer;
040import org.codehaus.plexus.context.Context;
041import org.codehaus.plexus.context.ContextException;
042import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
043
044/**
045 * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
046 */
047public abstract class AbstractEnforceMojo
048    extends AbstractMojo
049    implements Contextualizable
050{
051    /**
052     * This is a static variable used to persist the cached results across plugin invocations.
053     */
054    protected static Hashtable<String, EnforcerRule> cache = new Hashtable<String, EnforcerRule>();
055
056    /**
057     * Path Translator needed by the ExpressionEvaluator
058     */
059    @Component( role = PathTranslator.class )
060    protected PathTranslator translator;
061    
062    /**
063     * MojoExecution needed by the ExpressionEvaluator
064     */
065    @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
066    protected MojoExecution mojoExecution;
067
068    /**
069     * The MavenSession
070     */
071    @Parameter( defaultValue = "${session}", readonly = true, required = true )
072    protected MavenSession session;
073
074    /**
075     * POM
076     */
077    @Parameter( defaultValue = "${project}", readonly = true, required = true )
078    protected MavenProject project;
079
080    /**
081     * Flag to easily skip all checks
082     */
083    @Parameter( property = "enforcer.skip", defaultValue = "false" )
084    protected boolean skip = false;
085
086    /**
087     * Use this flag to disable rule result caching. This will cause all rules to execute on each project even if the
088     * rule indicates it can safely be cached.
089     */
090    @Parameter( property = "enforcer.ignoreCache", defaultValue = "false" )
091    protected boolean ignoreCache = false;
092
093    // set by the contextualize method. Only way to get the
094    // plugin's container in 2.0.x
095    protected PlexusContainer container;
096
097    public void contextualize( Context context )
098        throws ContextException
099    {
100        container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
101    }
102
103    /**
104     * Entry point to the mojo
105     * 
106     * @throws MojoExecutionException
107     */
108    public void execute()
109        throws MojoExecutionException
110    {
111        Log log = this.getLog();
112        EnforcerExpressionEvaluator evaluator = new EnforcerExpressionEvaluator( session, translator, project,
113                                                                                 mojoExecution );
114        // the entire execution can be easily skipped
115        if ( !skip )
116        {
117            // list to store exceptions
118            List<String> list = new ArrayList<String>();
119            // make sure the rules exist
120            if ( getRules() != null && getRules().length > 0 )
121            {
122                String currentRule = "Unknown";
123                // create my helper
124                EnforcerRuleHelper helper = new DefaultEnforcementRuleHelper( session, evaluator, log, container );
125                // if we are only warning, then disable
126                // failFast
127                if ( !isFail() )
128                {
129                    setFailFast( false );
130                }
131                // go through each rule
132                for ( int i = 0; i < getRules().length; i++ )
133                {
134                    // prevent against empty rules
135                    EnforcerRule rule = getRules()[i];
136                    if ( rule != null )
137                    {
138                        // store the current rule for
139                        // logging purposes
140                        currentRule = rule.getClass().getName();
141                        log.debug( "Executing rule: " + currentRule );
142                        try
143                        {
144                            if ( ignoreCache || shouldExecute( rule ) )
145                            {
146                                // execute the rule
147                                // noinspection SynchronizationOnLocalVariableOrMethodParameter
148                                synchronized ( rule )
149                                {
150                                    rule.execute( helper );
151                                }
152                            }
153                        }
154                        catch ( EnforcerRuleException e )
155                        {
156                            // i can throw an exception
157                            // because failfast will be
158                            // false if fail is false.
159                            if ( isFailFast() )
160                            {
161                                throw new MojoExecutionException( currentRule + " failed with message:\n"
162                                    + e.getMessage(), e );
163                            }
164                            else
165                            {
166                                list.add( createRuleMessage( i, currentRule, e ) );
167                                log.debug( "Adding failure due to exception", e );
168                            }
169                        }
170                    }
171                }
172                // if we found anything
173                if ( !list.isEmpty() )
174                {
175                    for ( String failure : list )
176                    {
177                        log.warn( failure );
178                    }
179                    if ( isFail() )
180                    {
181                        // CHECKSTYLE_OFF: LineLength
182                        throw new MojoExecutionException(
183                                                          "Some Enforcer rules have failed. Look above for specific messages explaining why the rule failed." );
184                        // CHECKSTYLE_ON: LineLength
185                    }
186                }
187            }
188            else
189            {
190                // CHECKSTYLE_OFF: LineLength
191                throw new MojoExecutionException(
192                                                  "No rules are configured. Use the skip flag if you want to disable execution." );
193                // CHECKSTYLE_ON: LineLength
194            }
195        }
196        else
197        {
198            log.info( "Skipping Rule Enforcement." );
199        }
200    }
201
202    /**
203     * This method determines if a rule should execute based on the cache
204     *
205     * @param rule the rule to verify
206     * @return {@code true} if rule should be executed, otherwise {@code false}
207     */
208    protected boolean shouldExecute( EnforcerRule rule )
209    {
210        if ( rule.isCacheable() )
211        {
212            Log log = this.getLog();
213            log.debug( "Rule " + rule.getClass().getName() + " is cacheable." );
214            String key = rule.getClass().getName() + " " + rule.getCacheId();
215            if ( EnforceMojo.cache.containsKey( key ) )
216            {
217                log.debug( "Key " + key + " was found in the cache" );
218                if ( rule.isResultValid( (EnforcerRule) cache.get( key ) ) )
219                {
220                    log.debug( "The cached results are still valid. Skipping the rule: " + rule.getClass().getName() );
221                    return false;
222                }
223            }
224            // add it to the cache of executed rules
225            EnforceMojo.cache.put( key, rule );
226        }
227        return true;
228    }
229
230    /**
231     * @return the fail
232     */
233    public abstract boolean isFail();
234
235    /**
236     * @return the rules
237     */
238    public abstract EnforcerRule[] getRules();
239
240    /**
241     * @param theRules to set.
242     */
243    public abstract void setRules( EnforcerRule[] theRules );
244
245    /**
246     * @return the skip
247     */
248    public boolean isSkip()
249    {
250        return this.skip;
251    }
252
253    /**
254     * @param theSkip the skip to set
255     */
256    public void setSkip( boolean theSkip )
257    {
258        this.skip = theSkip;
259    }
260
261    /**
262     * @return the failFast
263     */
264    public abstract boolean isFailFast();
265
266    /**
267     * @param failFast to set
268     */
269    public abstract void setFailFast( boolean failFast );
270
271    /**
272     * @return the project
273     */
274    public MavenProject getProject()
275    {
276        return this.project;
277    }
278
279    /**
280     * @param theProject the project to set
281     */
282    public void setProject( MavenProject theProject )
283    {
284        this.project = theProject;
285    }
286
287    /**
288     * @return the session
289     */
290    public MavenSession getSession()
291    {
292        return this.session;
293    }
294
295    /**
296     * @param theSession the session to set
297     */
298    public void setSession( MavenSession theSession )
299    {
300        this.session = theSession;
301    }
302
303    /**
304     * @return the translator
305     */
306    public PathTranslator getTranslator()
307    {
308        return this.translator;
309    }
310
311    /**
312     * @param theTranslator the translator to set
313     */
314    public void setTranslator( PathTranslator theTranslator )
315    {
316        this.translator = theTranslator;
317    }
318
319    /**
320     * Returns the error message displayed when failFast is set to false.
321     *
322     * @param i index
323     * @param currentRule name of the current rule.
324     * @param e rule exception
325     * @return rule message
326     */
327    protected abstract String createRuleMessage( int i, String currentRule, EnforcerRuleException e );
328}