001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.plugins.enforcer; 020 021import java.util.ArrayList; 022import java.util.Hashtable; 023import java.util.List; 024import java.util.Objects; 025import java.util.Optional; 026import java.util.stream.Collectors; 027 028import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule; 029import org.apache.maven.enforcer.rule.api.AbstractEnforcerRuleConfigProvider; 030import org.apache.maven.enforcer.rule.api.EnforcerLevel; 031import org.apache.maven.enforcer.rule.api.EnforcerRule; 032import org.apache.maven.enforcer.rule.api.EnforcerRuleBase; 033import org.apache.maven.enforcer.rule.api.EnforcerRuleError; 034import org.apache.maven.enforcer.rule.api.EnforcerRuleException; 035import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; 036import org.apache.maven.execution.MavenSession; 037import org.apache.maven.plugin.AbstractMojo; 038import org.apache.maven.plugin.MojoExecution; 039import org.apache.maven.plugin.MojoExecutionException; 040import org.apache.maven.plugin.PluginParameterExpressionEvaluator; 041import org.apache.maven.plugin.logging.Log; 042import org.apache.maven.plugins.annotations.Component; 043import org.apache.maven.plugins.annotations.LifecyclePhase; 044import org.apache.maven.plugins.annotations.Mojo; 045import org.apache.maven.plugins.annotations.Parameter; 046import org.apache.maven.plugins.annotations.ResolutionScope; 047import org.apache.maven.plugins.enforcer.internal.DefaultEnforcementRuleHelper; 048import org.apache.maven.plugins.enforcer.internal.EnforcerRuleCache; 049import org.apache.maven.plugins.enforcer.internal.EnforcerRuleDesc; 050import org.apache.maven.plugins.enforcer.internal.EnforcerRuleManager; 051import org.apache.maven.plugins.enforcer.internal.EnforcerRuleManagerException; 052import org.apache.maven.project.MavenProject; 053import org.codehaus.plexus.PlexusContainer; 054import org.codehaus.plexus.configuration.DefaultPlexusConfiguration; 055import org.codehaus.plexus.configuration.PlexusConfiguration; 056import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; 057import org.codehaus.plexus.util.StringUtils; 058 059/** 060 * This goal executes the defined enforcer-rules once per module. 061 * 062 * @author <a href="mailto:brianf@apache.org">Brian Fox</a> 063 */ 064@Mojo( 065 name = "enforce", 066 defaultPhase = LifecyclePhase.VALIDATE, 067 requiresDependencyCollection = ResolutionScope.TEST, 068 threadSafe = true) 069public class EnforceMojo extends AbstractMojo { 070 /** 071 * This is a static variable used to persist the cached results across plugin invocations. 072 */ 073 protected static Hashtable<String, EnforcerRule> cache = new Hashtable<>(); 074 075 /** 076 * MojoExecution needed by the ExpressionEvaluator 077 */ 078 @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true) 079 protected MojoExecution mojoExecution; 080 081 /** 082 * The MavenSession 083 */ 084 @Parameter(defaultValue = "${session}", readonly = true, required = true) 085 protected MavenSession session; 086 087 /** 088 * POM 089 */ 090 @Parameter(defaultValue = "${project}", readonly = true, required = true) 091 protected MavenProject project; 092 093 /** 094 * Flag to easily skip all checks 095 */ 096 @Parameter(property = "enforcer.skip", defaultValue = "false") 097 protected boolean skip = false; 098 099 /** 100 * Flag to fail the build if at least one check fails. 101 */ 102 @Parameter(property = "enforcer.fail", defaultValue = "true") 103 private boolean fail = true; 104 105 /** 106 * Fail on the first rule that doesn't pass 107 */ 108 @Parameter(property = "enforcer.failFast", defaultValue = "false") 109 private boolean failFast = false; 110 111 /** 112 * Flag to fail the build if no rules are present 113 * 114 * @since 3.2.0 115 */ 116 @Parameter(property = "enforcer.failIfNoRules", defaultValue = "true") 117 private boolean failIfNoRules = true; 118 119 /** 120 * Rules configuration to execute as XML. 121 * Each first level tag represents rule name to execute. 122 * Inner tags are configurations for rule. 123 * Eg: 124 * <pre> 125 * <rules> 126 * <alwaysFail/> 127 * <alwaysPass> 128 * <message>message for rule</message> 129 * </alwaysPass> 130 * <myRule implementation="org.example.MyRule"/> 131 * </rules> 132 * </pre> 133 * 134 * @since 1.0.0 135 */ 136 @Parameter 137 private PlexusConfiguration rules; 138 139 /** 140 * List of strings that matches the EnforcerRules to skip. 141 * 142 * @since 3.2.0 143 */ 144 @Parameter(required = false, property = "enforcer.skipRules") 145 private List<String> rulesToSkip; 146 147 /** 148 * Use this flag to disable rule result caching. This will cause all rules to execute on each project even if the 149 * rule indicates it can safely be cached. 150 */ 151 @Parameter(property = "enforcer.ignoreCache", defaultValue = "false") 152 protected boolean ignoreCache = false; 153 154 @Component 155 private PlexusContainer container; 156 157 @Component 158 private EnforcerRuleManager enforcerRuleManager; 159 160 @Component 161 private EnforcerRuleCache ruleCache; 162 163 private List<String> rulesToExecute; 164 165 /** 166 * List of strings that matches the EnforcerRules to execute. Replacement for the <code>rules</code> property. 167 * 168 * @param rulesToExecute a rules to execute 169 * @throws MojoExecutionException when values are incorrect 170 * @since 3.2.0 171 */ 172 @Parameter(required = false, property = "enforcer.rules") 173 public void setRulesToExecute(List<String> rulesToExecute) throws MojoExecutionException { 174 if (rulesToExecute != null && !rulesToExecute.isEmpty()) { 175 if (this.rulesToExecute != null && !this.rulesToExecute.isEmpty()) { 176 throw new MojoExecutionException("Detected the usage of both '-Drules' (which is deprecated) " 177 + "and '-Denforcer.rules'. Please use only one of them, preferably '-Denforcer.rules'."); 178 } 179 this.rulesToExecute = rulesToExecute; 180 } 181 } 182 183 /** 184 * List of strings that matches the EnforcerRules to execute. 185 * 186 * @param rulesToExecute a rules to execute 187 * @throws MojoExecutionException when values are incorrect 188 * @deprecated Use <code>enforcer.rules</code> property instead 189 */ 190 @Parameter(required = false, property = "rules") 191 @Deprecated 192 public void setCommandLineRules(List<String> rulesToExecute) throws MojoExecutionException { 193 if (rulesToExecute != null && !rulesToExecute.isEmpty()) { 194 getLog().warn( 195 "Detected the usage of property '-Drules' which is deprecated. Use '-Denforcer.rules' instead."); 196 } 197 setRulesToExecute(rulesToExecute); 198 } 199 200 @Override 201 public void execute() throws MojoExecutionException { 202 Log log = this.getLog(); 203 204 if (skip) { 205 log.info("Skipping Rule Enforcement."); 206 return; 207 } 208 209 Optional<PlexusConfiguration> rulesFromCommandLine = createRulesFromCommandLineOptions(); 210 List<EnforcerRuleDesc> rulesList; 211 212 // current behavior - rules from command line override all other configured rules. 213 List<EnforcerRuleDesc> allRules = enforcerRuleManager.createRules(rulesFromCommandLine.orElse(rules), log); 214 rulesList = filterOutSkippedRules(allRules); 215 216 List<EnforcerRuleDesc> additionalRules = processRuleConfigProviders(rulesList); 217 rulesList = filterOutRuleConfigProviders(rulesList); 218 rulesList.addAll(additionalRules); 219 220 if (rulesList.isEmpty()) { 221 if (failIfNoRules) { 222 throw new MojoExecutionException( 223 "No rules are configured. Use the skip flag if you want to disable execution."); 224 } else { 225 log.warn("No rules are configured."); 226 return; 227 } 228 } 229 230 // create my helper 231 PluginParameterExpressionEvaluator evaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); 232 EnforcerRuleHelper helper = new DefaultEnforcementRuleHelper(session, evaluator, log, container); 233 234 // if we are only warning, then disable 235 // failFast 236 if (!fail) { 237 failFast = false; 238 } 239 240 List<String> errorMessages = new ArrayList<>(); 241 242 // go through each rule 243 for (int ruleIndex = 0; ruleIndex < rulesList.size(); ruleIndex++) { 244 245 EnforcerRuleDesc ruleDesc = rulesList.get(ruleIndex); 246 EnforcerLevel level = ruleDesc.getLevel(); 247 try { 248 executeRule(ruleIndex, ruleDesc, helper); 249 } catch (EnforcerRuleError e) { 250 String ruleMessage = createRuleMessage(ruleIndex, ruleDesc, EnforcerLevel.ERROR, e); 251 throw new MojoExecutionException(System.lineSeparator() + ruleMessage, e); 252 } catch (EnforcerRuleException e) { 253 254 String ruleMessage = createRuleMessage(ruleIndex, ruleDesc, level, e); 255 256 if (failFast && level == EnforcerLevel.ERROR) { 257 throw new MojoExecutionException(System.lineSeparator() + ruleMessage, e); 258 } 259 260 if (level == EnforcerLevel.ERROR) { 261 errorMessages.add(ruleMessage); 262 } else { 263 log.warn(ruleMessage); 264 } 265 } 266 } 267 268 if (!errorMessages.isEmpty()) { 269 if (fail) { 270 throw new MojoExecutionException( 271 System.lineSeparator() + String.join(System.lineSeparator(), errorMessages)); 272 } else { 273 errorMessages.forEach(log::warn); 274 } 275 } 276 } 277 278 private List<EnforcerRuleDesc> processRuleConfigProviders(List<EnforcerRuleDesc> rulesList) { 279 return rulesList.stream() 280 .filter(Objects::nonNull) 281 .filter(rd -> rd.getRule() instanceof AbstractEnforcerRuleConfigProvider) 282 .map(this::executeRuleConfigProvider) 283 .flatMap(xml -> enforcerRuleManager.createRules(xml, getLog()).stream()) 284 .collect(Collectors.toList()); 285 } 286 287 private List<EnforcerRuleDesc> filterOutRuleConfigProviders(List<EnforcerRuleDesc> rulesList) { 288 return rulesList.stream() 289 .filter(Objects::nonNull) 290 .filter(rd -> !(rd.getRule() instanceof AbstractEnforcerRuleConfigProvider)) 291 .collect(Collectors.toList()); 292 } 293 294 private XmlPlexusConfiguration executeRuleConfigProvider(EnforcerRuleDesc ruleDesc) { 295 AbstractEnforcerRuleConfigProvider ruleProducer = (AbstractEnforcerRuleConfigProvider) ruleDesc.getRule(); 296 297 if (getLog().isDebugEnabled()) { 298 getLog().debug(String.format("Executing Rule Config Provider %s", ruleDesc.getRule())); 299 } 300 301 XmlPlexusConfiguration configuration = null; 302 try { 303 configuration = new XmlPlexusConfiguration(ruleProducer.getRulesConfig()); 304 } catch (EnforcerRuleException e) { 305 throw new EnforcerRuleManagerException("Rules Provider error for: " + getRuleName(ruleDesc), e); 306 } 307 getLog().info(String.format("Rule Config Provider %s executed", getRuleName(ruleDesc))); 308 309 return configuration; 310 } 311 312 private void executeRule(int ruleIndex, EnforcerRuleDesc ruleDesc, EnforcerRuleHelper helper) 313 throws EnforcerRuleException { 314 315 if (getLog().isDebugEnabled()) { 316 getLog().debug(String.format("Executing Rule %d: %s", ruleIndex, ruleDesc)); 317 } 318 319 long startTime = System.currentTimeMillis(); 320 321 try { 322 if (ruleDesc.getRule() instanceof EnforcerRule) { 323 executeRuleOld(ruleIndex, ruleDesc, helper); 324 } else if (ruleDesc.getRule() instanceof AbstractEnforcerRule) { 325 executeRuleNew(ruleIndex, ruleDesc); 326 } 327 } finally { 328 if (getLog().isDebugEnabled()) { 329 long workTime = System.currentTimeMillis() - startTime; 330 getLog().debug(String.format( 331 "Finish Rule %d: %s takes %d ms", ruleIndex, getRuleName(ruleDesc), workTime)); 332 } 333 } 334 } 335 336 private void executeRuleOld(int ruleIndex, EnforcerRuleDesc ruleDesc, EnforcerRuleHelper helper) 337 throws EnforcerRuleException { 338 339 EnforcerRule rule = (EnforcerRule) ruleDesc.getRule(); 340 341 if (ignoreCache || shouldExecute(rule)) { 342 rule.execute(helper); 343 getLog().info(String.format("Rule %d: %s passed", ruleIndex, getRuleName(ruleDesc))); 344 } 345 } 346 347 private void executeRuleNew(int ruleIndex, EnforcerRuleDesc ruleDesc) throws EnforcerRuleException { 348 349 AbstractEnforcerRule rule = (AbstractEnforcerRule) ruleDesc.getRule(); 350 if (ignoreCache || !ruleCache.isCached(rule)) { 351 rule.execute(); 352 getLog().info(String.format("Rule %d: %s passed", ruleIndex, getRuleName(ruleDesc))); 353 } 354 } 355 356 /** 357 * Create rules configuration based on command line provided rules list. 358 * 359 * @return an configuration in case where rules list is present or empty 360 */ 361 private Optional<PlexusConfiguration> createRulesFromCommandLineOptions() { 362 363 if (rulesToExecute == null || rulesToExecute.isEmpty()) { 364 return Optional.empty(); 365 } 366 367 PlexusConfiguration configuration = new DefaultPlexusConfiguration("rules"); 368 for (String rule : rulesToExecute) { 369 configuration.addChild(new DefaultPlexusConfiguration(rule)); 370 } 371 return Optional.of(configuration); 372 } 373 374 /** 375 * Filter out (remove) rules that have been specifically skipped via additional configuration. 376 * 377 * @param allRules list of enforcer rules to go through and filter 378 * @return list of filtered rules 379 */ 380 private List<EnforcerRuleDesc> filterOutSkippedRules(List<EnforcerRuleDesc> allRules) { 381 if (rulesToSkip == null || rulesToSkip.isEmpty()) { 382 return allRules; 383 } 384 return allRules.stream() 385 .filter(ruleDesc -> !rulesToSkip.contains(ruleDesc.getName())) 386 .collect(Collectors.toList()); 387 } 388 389 /** 390 * This method determines if a rule should execute based on the cache 391 * 392 * @param rule the rule to verify 393 * @return {@code true} if rule should be executed, otherwise {@code false} 394 */ 395 protected boolean shouldExecute(EnforcerRule rule) { 396 if (rule.isCacheable()) { 397 Log log = this.getLog(); 398 log.debug("Rule " + rule.getClass().getName() + " is cacheable."); 399 String key = rule.getClass().getName() + " " + rule.getCacheId(); 400 if (EnforceMojo.cache.containsKey(key)) { 401 log.debug("Key " + key + " was found in the cache"); 402 if (rule.isResultValid(cache.get(key))) { 403 log.debug("The cached results are still valid. Skipping the rule: " 404 + rule.getClass().getName()); 405 return false; 406 } 407 } 408 409 // add it to the cache of executed rules 410 EnforceMojo.cache.put(key, rule); 411 } 412 return true; 413 } 414 415 /** 416 * Set rule list to skip. 417 * 418 * @param rulesToSkip a rule list 419 */ 420 public void setRulesToSkip(List<String> rulesToSkip) { 421 if (rulesToSkip == null) { 422 return; 423 } 424 // internally all rules begin from lowercase letter 425 this.rulesToSkip = rulesToSkip.stream() 426 .filter(Objects::nonNull) 427 .map(StringUtils::lowercaseFirstLetter) 428 .collect(Collectors.toList()); 429 } 430 431 /** 432 * @param theFail the fail to set 433 */ 434 public void setFail(boolean theFail) { 435 this.fail = theFail; 436 } 437 438 /** 439 * @param theFailFast the failFast to set 440 */ 441 public void setFailFast(boolean theFailFast) { 442 this.failFast = theFailFast; 443 } 444 445 private String createRuleMessage( 446 int ruleIndex, EnforcerRuleDesc ruleDesc, EnforcerLevel level, EnforcerRuleException e) { 447 448 StringBuilder result = new StringBuilder(); 449 result.append("Rule ").append(ruleIndex).append(": ").append(getRuleName(ruleDesc)); 450 451 if (level == EnforcerLevel.ERROR) { 452 result.append(" failed"); 453 } else { 454 result.append(" warned"); 455 } 456 457 if (e.getMessage() != null) { 458 result.append(" with message:").append(System.lineSeparator()).append(e.getMessage()); 459 } else { 460 result.append(" without a message"); 461 } 462 463 return result.toString(); 464 } 465 466 private String getRuleName(EnforcerRuleDesc ruleDesc) { 467 468 Class<? extends EnforcerRuleBase> ruleClass = ruleDesc.getRule().getClass(); 469 470 String ruleName = ruleClass.getName(); 471 472 if (!ruleClass.getSimpleName().equalsIgnoreCase(ruleDesc.getName())) { 473 ruleName += "(" + ruleDesc.getName() + ")"; 474 } 475 476 return ruleName; 477 } 478 479 /** 480 * @param thefailIfNoRules the failIfNoRules to set 481 */ 482 public void setFailIfNoRules(boolean thefailIfNoRules) { 483 this.failIfNoRules = thefailIfNoRules; 484 } 485}