View Javadoc
1   package org.apache.maven.plugins.enforcer;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Hashtable;
24  import java.util.List;
25  
26  import org.apache.maven.enforcer.rule.api.EnforcerLevel;
27  import org.apache.maven.enforcer.rule.api.EnforcerRule;
28  import org.apache.maven.enforcer.rule.api.EnforcerRule2;
29  import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
30  import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
31  import org.apache.maven.execution.MavenSession;
32  import org.apache.maven.plugin.AbstractMojo;
33  import org.apache.maven.plugin.MojoExecution;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.logging.Log;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.apache.maven.project.MavenProject;
41  import org.codehaus.plexus.PlexusConstants;
42  import org.codehaus.plexus.PlexusContainer;
43  import org.codehaus.plexus.context.Context;
44  import org.codehaus.plexus.context.ContextException;
45  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
46  
47  /**
48   * This goal executes the defined enforcer-rules once per module.
49   *
50   * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
51   */
52  // CHECKSTYLE_OFF: LineLength
53  @Mojo( name = "enforce", defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyCollection = ResolutionScope.TEST, threadSafe = true )
54  //CHECKSTYLE_ON: LineLength
55  public class EnforceMojo
56      extends AbstractMojo
57      implements Contextualizable
58  {
59      /**
60       * This is a static variable used to persist the cached results across plugin invocations.
61       */
62      protected static Hashtable<String, EnforcerRule> cache = new Hashtable<String, EnforcerRule>();
63  
64      /**
65       * MojoExecution needed by the ExpressionEvaluator
66       */
67      @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
68      protected MojoExecution mojoExecution;
69  
70      /**
71       * The MavenSession
72       */
73      @Parameter( defaultValue = "${session}", readonly = true, required = true )
74      protected MavenSession session;
75  
76      /**
77       * POM
78       */
79      @Parameter( defaultValue = "${project}", readonly = true, required = true )
80      protected MavenProject project;
81  
82      /**
83       * Flag to easily skip all checks
84       */
85      @Parameter( property = "enforcer.skip", defaultValue = "false" )
86      protected boolean skip = false;
87  
88      /**
89       * Flag to fail the build if a version check fails.
90       */
91      @Parameter( property = "enforcer.fail", defaultValue = "true" )
92      private boolean fail = true;
93  
94      /**
95       * Fail on the first rule that doesn't pass
96       */
97      @Parameter( property = "enforcer.failFast", defaultValue = "false" )
98      private boolean failFast = false;
99  
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     /**
136      * Entry point to the mojo
137      * 
138      * @throws MojoExecutionException
139      */
140     @Override
141     public void execute()
142         throws MojoExecutionException
143     {
144         Log log = this.getLog();
145 
146         EnforcerExpressionEvaluator evaluator =
147             new EnforcerExpressionEvaluator( session, mojoExecution );
148         if ( commandLineRules != null && commandLineRules.length > 0 )
149         {
150             this.rules = createRulesFromCommandLineOptions();
151         }
152         
153         if ( isSkip() )
154         {
155             log.info( "Skipping Rule Enforcement." );
156             return;
157         }
158 
159         if ( !havingRules() )
160         {
161             // CHECKSTYLE_OFF: LineLength
162             throw new MojoExecutionException( "No rules are configured. Use the skip flag if you want to disable execution." );
163             // CHECKSTYLE_ON: LineLength
164         }
165 
166         // list to store exceptions
167         List<String> list = new ArrayList<String>();
168 
169         String currentRule = "Unknown";
170 
171         // create my helper
172         EnforcerRuleHelper helper = new DefaultEnforcementRuleHelper( session, evaluator, log, container );
173 
174         // if we are only warning, then disable
175         // failFast
176         if ( !fail )
177         {
178             failFast = false;
179         }
180 
181         boolean hasErrors = false;
182 
183         // go through each rule
184         for ( int i = 0; i < rules.length; i++ )
185         {
186 
187             // prevent against empty rules
188             EnforcerRule rule = rules[i];
189             final EnforcerLevel level = getLevel( rule );
190             if ( rule != null )
191             {
192                 // store the current rule for
193                 // logging purposes
194                 currentRule = rule.getClass().getName();
195                 log.debug( "Executing rule: " + currentRule );
196                 try
197                 {
198                     if ( ignoreCache || shouldExecute( rule ) )
199                     {
200                         // execute the rule
201                         // noinspection
202                         // SynchronizationOnLocalVariableOrMethodParameter
203                         synchronized ( rule )
204                         {
205                             rule.execute( helper );
206                         }
207                     }
208                 }
209                 catch ( EnforcerRuleException e )
210                 {
211                     // i can throw an exception
212                     // because failfast will be
213                     // false if fail is false.
214                     if ( failFast && level == EnforcerLevel.ERROR )
215                     {
216                         throw new MojoExecutionException( currentRule + " failed with message:"
217                             + System.lineSeparator() + e.getMessage(), e );
218                     }
219                     else
220                     {
221                         // log a warning in case the exception message is missing
222                         // so that the user can figure out what is going on
223                         final String exceptionMessage = e.getMessage();
224                         if ( exceptionMessage != null )
225                         {
226                             log.debug( "Adding " + level + " message due to exception", e );
227                         }
228                         else
229                         {
230                             log.warn( "Rule " + i + ": " + currentRule + " failed without a message", e );
231                         }
232                         // add the 'failed/warned' message including exceptionMessage
233                         // which might be null in rare cases
234                         if ( level == EnforcerLevel.ERROR )
235                         {
236                             hasErrors = true;
237                             list.add( "Rule " + i + ": " + currentRule + " failed with message:"
238                                  + System.lineSeparator() + exceptionMessage );
239                         }
240                         else
241                         {
242                             list.add( "Rule " + i + ": " + currentRule + " warned with message:"
243                                  + System.lineSeparator() + exceptionMessage );
244                         }
245                     }
246                 }
247             }
248         }
249 
250         // if we found anything
251         // CHECKSTYLE_OFF: LineLength
252         if ( !list.isEmpty() )
253         {
254             for ( String failure : list )
255             {
256                 log.warn( failure );
257             }
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         }
263         // CHECKSTYLE_ON: LineLength
264     }
265 
266     private EnforcerRule[] createRulesFromCommandLineOptions() throws MojoExecutionException 
267     {
268         EnforcerRule[] rules = new EnforcerRule[commandLineRules.length];
269         for ( int i = 0; i < commandLineRules.length; i++ ) 
270         {
271             String rule = commandLineRules[i];
272             if ( !rule.contains( "." ) )
273             {
274                 rule = getClass().getPackage().getName() 
275                     + "." + Character.toUpperCase( rule.charAt( 0 ) ) + rule.substring( 1 ); 
276             }
277             
278             try 
279             {
280                 rules[i] = ( EnforcerRule ) Class.forName( rule ).newInstance();
281             }
282             catch ( Exception e ) 
283             {
284                 throw new MojoExecutionException( "Failed to create enforcer rules from command line argument", e );
285             }
286         }
287         return rules;
288     }
289 
290     /**
291      * This method determines if a rule should execute based on the cache
292      *
293      * @param rule the rule to verify
294      * @return {@code true} if rule should be executed, otherwise {@code false}
295      */
296     protected boolean shouldExecute( EnforcerRule rule )
297     {
298         if ( rule.isCacheable() )
299         {
300             Log log = this.getLog();
301             log.debug( "Rule " + rule.getClass().getName() + " is cacheable." );
302             String key = rule.getClass().getName() + " " + rule.getCacheId();
303             if ( EnforceMojo.cache.containsKey( key ) )
304             {
305                 log.debug( "Key " + key + " was found in the cache" );
306                 if ( rule.isResultValid( cache.get( key ) ) )
307                 {
308                     log.debug( "The cached results are still valid. Skipping the rule: " + rule.getClass().getName() );
309                     return false;
310                 }
311             }
312 
313             // add it to the cache of executed rules
314             EnforceMojo.cache.put( key, rule );
315         }
316         return true;
317     }
318 
319     /**
320      * @return the fail
321      */
322     public boolean isFail()
323     {
324         return this.fail;
325     }
326 
327     /**
328      * @param theFail the fail to set
329      */
330     public void setFail( boolean theFail )
331     {
332         this.fail = theFail;
333     }
334 
335     /**
336      * @return the rules
337      */
338     public EnforcerRule[] getRules()
339     {
340         return this.rules;
341     }
342 
343     /**
344      * @param theRules the rules to set
345      */
346     public void setRules( EnforcerRule[] theRules )
347     {
348         this.rules = theRules;
349     }
350 
351     /**
352      * @param theFailFast the failFast to set
353      */
354     public void setFailFast( boolean theFailFast )
355     {
356         this.failFast = theFailFast;
357     }
358 
359     public boolean isFailFast()
360     {
361         return failFast;
362     }
363 
364     protected String createRuleMessage( int i, String currentRule, EnforcerRuleException e )
365     {
366         return "Rule " + i + ": " + currentRule + " failed with message:" + System.lineSeparator() + e.getMessage();
367     }
368 
369     /**
370      * Returns the level of the rule, defaults to {@link EnforcerLevel#ERROR} for backwards compatibility.
371      *
372      * @param rule might be of type {@link EnforcerRule2}.
373      * @return level of the rule.
374      */
375     private EnforcerLevel getLevel( EnforcerRule rule )
376     {
377         if ( rule instanceof EnforcerRule2 )
378         {
379             return ( (EnforcerRule2) rule ).getLevel();
380         }
381         else
382         {
383             return EnforcerLevel.ERROR;
384         }
385     }
386 
387     /**
388      * @return the skip
389      */
390     public boolean isSkip()
391     {
392         return this.skip;
393     }
394 
395     /**
396      * @param theSkip the skip to set
397      */
398     public void setSkip( boolean theSkip )
399     {
400         this.skip = theSkip;
401     }
402 
403     /**
404      * @return the project
405      */
406     public MavenProject getProject()
407     {
408         return this.project;
409     }
410 
411     /**
412      * @param theProject the project to set
413      */
414     public void setProject( MavenProject theProject )
415     {
416         this.project = theProject;
417     }
418 
419     /**
420      * @return the session
421      */
422     public MavenSession getSession()
423     {
424         return this.session;
425     }
426 
427     /**
428      * @param theSession the session to set
429      */
430     public void setSession( MavenSession theSession )
431     {
432         this.session = theSession;
433     }
434 
435 }