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