001package org.apache.maven.model.validation; 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.io.File; 023import java.util.Arrays; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.regex.Pattern; 030 031import org.apache.maven.model.Build; 032import org.apache.maven.model.BuildBase; 033import org.apache.maven.model.Dependency; 034import org.apache.maven.model.DependencyManagement; 035import org.apache.maven.model.DistributionManagement; 036import org.apache.maven.model.Exclusion; 037import org.apache.maven.model.InputLocation; 038import org.apache.maven.model.InputLocationTracker; 039import org.apache.maven.model.Model; 040import org.apache.maven.model.Parent; 041import org.apache.maven.model.Plugin; 042import org.apache.maven.model.PluginExecution; 043import org.apache.maven.model.PluginManagement; 044import org.apache.maven.model.Profile; 045import org.apache.maven.model.ReportPlugin; 046import org.apache.maven.model.Reporting; 047import org.apache.maven.model.Repository; 048import org.apache.maven.model.Resource; 049import org.apache.maven.model.building.ModelBuildingRequest; 050import org.apache.maven.model.building.ModelProblem.Severity; 051import org.apache.maven.model.building.ModelProblem.Version; 052import org.apache.maven.model.building.ModelProblemCollector; 053import org.apache.maven.model.building.ModelProblemCollectorRequest; 054import org.codehaus.plexus.component.annotations.Component; 055import org.codehaus.plexus.util.StringUtils; 056 057/** 058 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> 059 */ 060@Component( role = ModelValidator.class ) 061public class DefaultModelValidator 062 implements ModelValidator 063{ 064 065 private static final Pattern ID_REGEX = Pattern.compile( "[A-Za-z0-9_\\-.]+" ); 066 067 private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*"; 068 069 private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS; 070 071 private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS; 072 073 public void validateRawModel( Model model, ModelBuildingRequest request, ModelProblemCollector problems ) 074 { 075 Parent parent = model.getParent(); 076 if ( parent != null ) 077 { 078 validateStringNotEmpty( "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(), parent ); 079 080 validateStringNotEmpty( "parent.artifactId", problems, Severity.FATAL, Version.BASE, parent.getArtifactId(), parent ); 081 082 validateStringNotEmpty( "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(), parent ); 083 084 if ( equals( parent.getGroupId(), model.getGroupId() ) 085 && equals( parent.getArtifactId(), model.getArtifactId() ) ) 086 { 087 addViolation( problems, Severity.FATAL, Version.BASE, "parent.artifactId", null, "must be changed" 088 + ", the parent element cannot have the same groupId:artifactId as the project.", parent ); 089 } 090 } 091 092 if ( request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 ) 093 { 094 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 095 096 validateEnum( "modelVersion", problems, Severity.ERROR, Version.V20, model.getModelVersion(), null, model, "4.0.0" ); 097 098 validateStringNoExpression( "groupId", problems, Severity.WARNING, Version.V20, model.getGroupId(), model ); 099 if ( parent == null ) 100 { 101 validateStringNotEmpty( "groupId", problems, Severity.FATAL, Version.V20, model.getGroupId(), model ); 102 } 103 104 validateStringNoExpression( "artifactId", problems, Severity.WARNING, Version.V20, model.getArtifactId(), model ); 105 validateStringNotEmpty( "artifactId", problems, Severity.FATAL, Version.V20, model.getArtifactId(), model ); 106 107 validateStringNoExpression( "version", problems, Severity.WARNING, Version.V20, model.getVersion(), model ); 108 if ( parent == null ) 109 { 110 validateStringNotEmpty( "version", problems, Severity.FATAL, Version.V20, model.getVersion(), model ); 111 } 112 113 validate20RawDependencies( problems, model.getDependencies(), "dependencies.dependency", request ); 114 115 if ( model.getDependencyManagement() != null ) 116 { 117 validate20RawDependencies( problems, model.getDependencyManagement().getDependencies(), 118 "dependencyManagement.dependencies.dependency", request ); 119 } 120 121 validateRawRepositories( problems, model.getRepositories(), "repositories.repository", request ); 122 123 validateRawRepositories( problems, model.getPluginRepositories(), "pluginRepositories.pluginRepository", 124 request ); 125 126 Build build = model.getBuild(); 127 if ( build != null ) 128 { 129 validate20RawPlugins( problems, build.getPlugins(), "build.plugins.plugin", request ); 130 131 PluginManagement mngt = build.getPluginManagement(); 132 if ( mngt != null ) 133 { 134 validate20RawPlugins( problems, mngt.getPlugins(), "build.pluginManagement.plugins.plugin", 135 request ); 136 } 137 } 138 139 Set<String> profileIds = new HashSet<String>(); 140 141 for ( Profile profile : model.getProfiles() ) 142 { 143 String prefix = "profiles.profile[" + profile.getId() + "]"; 144 145 if ( !profileIds.add( profile.getId() ) ) 146 { 147 addViolation( problems, errOn30, Version.V20, "profiles.profile.id", null, 148 "must be unique but found duplicate profile with id " + profile.getId(), profile ); 149 } 150 151 validate20RawDependencies( problems, profile.getDependencies(), prefix + ".dependencies.dependency", 152 request ); 153 154 if ( profile.getDependencyManagement() != null ) 155 { 156 validate20RawDependencies( problems, profile.getDependencyManagement().getDependencies(), prefix 157 + ".dependencyManagement.dependencies.dependency", request ); 158 } 159 160 validateRawRepositories( problems, profile.getRepositories(), prefix + ".repositories.repository", 161 request ); 162 163 validateRawRepositories( problems, profile.getPluginRepositories(), prefix 164 + ".pluginRepositories.pluginRepository", request ); 165 166 BuildBase buildBase = profile.getBuild(); 167 if ( buildBase != null ) 168 { 169 validate20RawPlugins( problems, buildBase.getPlugins(), prefix + ".plugins.plugin", request ); 170 171 PluginManagement mngt = buildBase.getPluginManagement(); 172 if ( mngt != null ) 173 { 174 validate20RawPlugins( problems, mngt.getPlugins(), prefix + ".pluginManagement.plugins.plugin", 175 request ); 176 } 177 } 178 } 179 } 180 } 181 182 private void validate20RawPlugins( ModelProblemCollector problems, List<Plugin> plugins, String prefix, 183 ModelBuildingRequest request ) 184 { 185 Severity errOn31 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1 ); 186 187 Map<String, Plugin> index = new HashMap<String, Plugin>(); 188 189 for ( Plugin plugin : plugins ) 190 { 191 String key = plugin.getKey(); 192 193 Plugin existing = index.get( key ); 194 195 if ( existing != null ) 196 { 197 addViolation( problems, errOn31, Version.V20, prefix + ".(groupId:artifactId)", null, 198 "must be unique but found duplicate declaration of plugin " + key, plugin ); 199 } 200 else 201 { 202 index.put( key, plugin ); 203 } 204 205 Set<String> executionIds = new HashSet<String>(); 206 207 for ( PluginExecution exec : plugin.getExecutions() ) 208 { 209 if ( !executionIds.add( exec.getId() ) ) 210 { 211 addViolation( problems, Severity.ERROR, Version.V20, prefix + "[" + plugin.getKey() 212 + "].executions.execution.id", null, "must be unique but found duplicate execution with id " 213 + exec.getId(), exec ); 214 } 215 } 216 } 217 } 218 219 public void validateEffectiveModel( Model model, ModelBuildingRequest request, ModelProblemCollector problems ) 220 { 221 validateStringNotEmpty( "modelVersion", problems, Severity.ERROR, Version.BASE, model.getModelVersion(), model ); 222 223 validateId( "groupId", problems, model.getGroupId(), model ); 224 225 validateId( "artifactId", problems, model.getArtifactId(), model ); 226 227 validateStringNotEmpty( "packaging", problems, Severity.ERROR, Version.BASE, model.getPackaging(), model ); 228 229 if ( !model.getModules().isEmpty() ) 230 { 231 if ( !"pom".equals( model.getPackaging() ) ) 232 { 233 addViolation( problems, Severity.ERROR, Version.BASE, "packaging", null, 234 "with value '" + model.getPackaging() + "' is invalid. Aggregator projects " 235 + "require 'pom' as packaging.", model ); 236 } 237 238 for ( int i = 0, n = model.getModules().size(); i < n; i++ ) 239 { 240 String module = model.getModules().get( i ); 241 if ( StringUtils.isBlank( module ) ) 242 { 243 addViolation( problems, Severity.WARNING, Version.BASE, "modules.module[" + i + "]", null, 244 "has been specified without a path to the project directory.", 245 model.getLocation( "modules" ) ); 246 } 247 } 248 } 249 250 validateStringNotEmpty( "version", problems, Severity.ERROR, Version.BASE, model.getVersion(), model ); 251 252 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 253 254 validateEffectiveDependencies( problems, model.getDependencies(), false, request ); 255 256 DependencyManagement mgmt = model.getDependencyManagement(); 257 if ( mgmt != null ) 258 { 259 validateEffectiveDependencies( problems, mgmt.getDependencies(), true, request ); 260 } 261 262 if ( request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 ) 263 { 264 Set<String> modules = new HashSet<String>(); 265 for ( int i = 0, n = model.getModules().size(); i < n; i++ ) 266 { 267 String module = model.getModules().get( i ); 268 if ( !modules.add( module ) ) 269 { 270 addViolation( problems, Severity.ERROR, Version.V20, "modules.module[" + i + "]", null, 271 "specifies duplicate child module " + module, model.getLocation( "modules" ) ); 272 } 273 } 274 275 Severity errOn31 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1 ); 276 277 validateBannedCharacters( "version", problems, errOn31, Version.V20, model.getVersion(), null, model, 278 ILLEGAL_VERSION_CHARS ); 279 validate20ProperSnapshotVersion( "version", problems, errOn31, Version.V20, model.getVersion(), null, model ); 280 281 Build build = model.getBuild(); 282 if ( build != null ) 283 { 284 for ( Plugin p : build.getPlugins() ) 285 { 286 validateStringNotEmpty( "build.plugins.plugin.artifactId", problems, Severity.ERROR, Version.V20, 287 p.getArtifactId(), p ); 288 289 validateStringNotEmpty( "build.plugins.plugin.groupId", problems, Severity.ERROR, Version.V20, p.getGroupId(), 290 p ); 291 292 validate20PluginVersion( "build.plugins.plugin.version", problems, p.getVersion(), p.getKey(), p, 293 request ); 294 295 validateBoolean( "build.plugins.plugin.inherited", problems, errOn30, Version.V20, p.getInherited(), p.getKey(), 296 p ); 297 298 validateBoolean( "build.plugins.plugin.extensions", problems, errOn30, Version.V20, p.getExtensions(), 299 p.getKey(), p ); 300 301 validate20EffectivePluginDependencies( problems, p, request ); 302 } 303 304 validate20RawResources( problems, build.getResources(), "build.resources.resource", request ); 305 306 validate20RawResources( problems, build.getTestResources(), "build.testResources.testResource", request ); 307 } 308 309 Reporting reporting = model.getReporting(); 310 if ( reporting != null ) 311 { 312 for ( ReportPlugin p : reporting.getPlugins() ) 313 { 314 validateStringNotEmpty( "reporting.plugins.plugin.artifactId", problems, Severity.ERROR, Version.V20, 315 p.getArtifactId(), p ); 316 317 validateStringNotEmpty( "reporting.plugins.plugin.groupId", problems, Severity.ERROR, Version.V20, 318 p.getGroupId(), p ); 319 } 320 } 321 322 for ( Repository repository : model.getRepositories() ) 323 { 324 validate20EffectiveRepository( problems, repository, "repositories.repository", request ); 325 } 326 327 for ( Repository repository : model.getPluginRepositories() ) 328 { 329 validate20EffectiveRepository( problems, repository, "pluginRepositories.pluginRepository", request ); 330 } 331 332 DistributionManagement distMgmt = model.getDistributionManagement(); 333 if ( distMgmt != null ) 334 { 335 if ( distMgmt.getStatus() != null ) 336 { 337 addViolation( problems, Severity.ERROR, Version.V20, "distributionManagement.status", null, 338 "must not be specified.", distMgmt ); 339 } 340 341 validate20EffectiveRepository( problems, distMgmt.getRepository(), "distributionManagement.repository", request ); 342 validate20EffectiveRepository( problems, distMgmt.getSnapshotRepository(), 343 "distributionManagement.snapshotRepository", request ); 344 } 345 } 346 } 347 348 private void validate20RawDependencies( ModelProblemCollector problems, List<Dependency> dependencies, String prefix, 349 ModelBuildingRequest request ) 350 { 351 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 352 Severity errOn31 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1 ); 353 354 Map<String, Dependency> index = new HashMap<String, Dependency>(); 355 356 for ( Dependency dependency : dependencies ) 357 { 358 String key = dependency.getManagementKey(); 359 360 if ( "import".equals( dependency.getScope() ) ) 361 { 362 if ( !"pom".equals( dependency.getType() ) ) 363 { 364 addViolation( problems, Severity.WARNING, Version.V20, prefix + ".type", key, 365 "must be 'pom' to import the managed dependencies.", dependency ); 366 } 367 else if ( StringUtils.isNotEmpty( dependency.getClassifier() ) ) 368 { 369 addViolation( problems, errOn30, Version.V20, prefix + ".classifier", key, 370 "must be empty, imported POM cannot have a classifier.", dependency ); 371 } 372 } 373 else if ( "system".equals( dependency.getScope() ) ) 374 { 375 String sysPath = dependency.getSystemPath(); 376 if ( StringUtils.isNotEmpty( sysPath ) ) 377 { 378 if ( !hasExpression( sysPath ) ) 379 { 380 addViolation( problems, Severity.WARNING, Version.V20, prefix + ".systemPath", key, 381 "should use a variable instead of a hard-coded path " + sysPath, dependency ); 382 } 383 else if ( sysPath.contains( "${basedir}" ) || sysPath.contains( "${project.basedir}" ) ) 384 { 385 addViolation( problems, Severity.WARNING, Version.V20, prefix + ".systemPath", key, 386 "should not point at files within the project directory, " + sysPath 387 + " will be unresolvable by dependent projects", dependency ); 388 } 389 } 390 } 391 392 Dependency existing = index.get( key ); 393 394 if ( existing != null ) 395 { 396 String msg; 397 if ( equals( existing.getVersion(), dependency.getVersion() ) ) 398 { 399 msg = 400 "duplicate declaration of version " 401 + StringUtils.defaultString( dependency.getVersion(), "(?)" ); 402 } 403 else 404 { 405 msg = 406 "version " + StringUtils.defaultString( existing.getVersion(), "(?)" ) + " vs " 407 + StringUtils.defaultString( dependency.getVersion(), "(?)" ); 408 } 409 410 addViolation( problems, errOn31, Version.V20, prefix + ".(groupId:artifactId:type:classifier)", null, 411 "must be unique: " + key + " -> " + msg, dependency ); 412 } 413 else 414 { 415 index.put( key, dependency ); 416 } 417 } 418 } 419 420 private void validateEffectiveDependencies( ModelProblemCollector problems, List<Dependency> dependencies, 421 boolean management, ModelBuildingRequest request ) 422 { 423 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 424 425 String prefix = management ? "dependencyManagement.dependencies.dependency." : "dependencies.dependency."; 426 427 for ( Dependency d : dependencies ) 428 { 429 validateEffectiveDependency( problems, d, management, prefix, request ); 430 431 if ( request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 ) 432 { 433 validateBoolean( prefix + "optional", problems, errOn30, Version.V20, d.getOptional(), d.getManagementKey(), d ); 434 435 if ( !management ) 436 { 437 validateVersion( prefix + "version", problems, errOn30, Version.V20, d.getVersion(), d.getManagementKey(), d ); 438 439 /* 440 * TODO: Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. 441 * In order to don't break backward-compat with those, only warn but don't error out. 442 */ 443 validateEnum( prefix + "scope", problems, Severity.WARNING, Version.V20, d.getScope(), d.getManagementKey(), d, 444 "provided", "compile", "runtime", "test", "system" ); 445 } 446 } 447 } 448 } 449 450 private void validate20EffectivePluginDependencies( ModelProblemCollector problems, Plugin plugin, 451 ModelBuildingRequest request ) 452 { 453 List<Dependency> dependencies = plugin.getDependencies(); 454 455 if ( !dependencies.isEmpty() ) 456 { 457 String prefix = "build.plugins.plugin[" + plugin.getKey() + "].dependencies.dependency."; 458 459 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 460 461 for ( Dependency d : dependencies ) 462 { 463 validateEffectiveDependency( problems, d, false, prefix, request ); 464 465 validateVersion( prefix + "version", problems, errOn30, Version.BASE, d.getVersion(), d.getManagementKey(), d ); 466 467 validateEnum( prefix + "scope", problems, errOn30, Version.BASE, d.getScope(), d.getManagementKey(), d, "compile", 468 "runtime", "system" ); 469 } 470 } 471 } 472 473 private void validateEffectiveDependency( ModelProblemCollector problems, Dependency d, boolean management, 474 String prefix, ModelBuildingRequest request ) 475 { 476 validateId( prefix + "artifactId", problems, Severity.ERROR, Version.BASE, d.getArtifactId(), d.getManagementKey(), d ); 477 478 validateId( prefix + "groupId", problems, Severity.ERROR, Version.BASE, d.getGroupId(), d.getManagementKey(), d ); 479 480 if ( !management ) 481 { 482 validateStringNotEmpty( prefix + "type", problems, Severity.ERROR, Version.BASE, d.getType(), d.getManagementKey(), d ); 483 484 validateStringNotEmpty( prefix + "version", problems, Severity.ERROR, Version.BASE, d.getVersion(), d.getManagementKey(), 485 d ); 486 } 487 488 if ( "system".equals( d.getScope() ) ) 489 { 490 String systemPath = d.getSystemPath(); 491 492 if ( StringUtils.isEmpty( systemPath ) ) 493 { 494 addViolation( problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(), "is missing.", 495 d ); 496 } 497 else 498 { 499 File sysFile = new File( systemPath ); 500 if ( !sysFile.isAbsolute() ) 501 { 502 addViolation( problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(), 503 "must specify an absolute path but is " + systemPath, d ); 504 } 505 else if ( !sysFile.isFile() ) 506 { 507 String msg = "refers to a non-existing file " + sysFile.getAbsolutePath(); 508 systemPath = systemPath.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); 509 String jdkHome = 510 request.getSystemProperties().getProperty( "java.home", "" ) + File.separator + ".."; 511 if ( systemPath.startsWith( jdkHome ) ) 512 { 513 msg += ". Please verify that you run Maven using a JDK and not just a JRE."; 514 } 515 addViolation( problems, Severity.WARNING, Version.BASE, prefix + "systemPath", d.getManagementKey(), msg, d ); 516 } 517 } 518 } 519 else if ( StringUtils.isNotEmpty( d.getSystemPath() ) ) 520 { 521 addViolation( problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(), "must be omitted." 522 + " This field may only be specified for a dependency with system scope.", d ); 523 } 524 525 if ( request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 ) 526 { 527 for ( Exclusion exclusion : d.getExclusions() ) 528 { 529 validateId( prefix + "exclusions.exclusion.groupId", problems, Severity.WARNING, Version.V20, 530 exclusion.getGroupId(), d.getManagementKey(), exclusion ); 531 532 validateId( prefix + "exclusions.exclusion.artifactId", problems, Severity.WARNING, Version.V20, 533 exclusion.getArtifactId(), d.getManagementKey(), exclusion ); 534 } 535 } 536 } 537 538 private void validateRawRepositories( ModelProblemCollector problems, List<Repository> repositories, String prefix, 539 ModelBuildingRequest request ) 540 { 541 Map<String, Repository> index = new HashMap<String, Repository>(); 542 543 for ( Repository repository : repositories ) 544 { 545 validateStringNotEmpty( prefix + ".id", problems, Severity.ERROR, Version.V20, repository.getId(), repository ); 546 547 validateStringNotEmpty( prefix + "[" + repository.getId() + "].url", problems, Severity.ERROR, Version.V20, 548 repository.getUrl(), repository ); 549 550 String key = repository.getId(); 551 552 Repository existing = index.get( key ); 553 554 if ( existing != null ) 555 { 556 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 557 558 addViolation( problems, errOn30, Version.V20, prefix + ".id", null, "must be unique: " + repository.getId() + " -> " 559 + existing.getUrl() + " vs " + repository.getUrl(), repository ); 560 } 561 else 562 { 563 index.put( key, repository ); 564 } 565 } 566 } 567 568 private void validate20EffectiveRepository( ModelProblemCollector problems, Repository repository, String prefix, 569 ModelBuildingRequest request ) 570 { 571 if ( repository != null ) 572 { 573 Severity errOn31 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1 ); 574 575 validateBannedCharacters( prefix + ".id", problems, errOn31, Version.V20, repository.getId(), null, repository, 576 ILLEGAL_REPO_ID_CHARS ); 577 578 if ( "local".equals( repository.getId() ) ) 579 { 580 addViolation( problems, errOn31, Version.V20, prefix + ".id", null, "must not be 'local'" 581 + ", this identifier is reserved for the local repository" 582 + ", using it for other repositories will corrupt your repository metadata.", repository ); 583 } 584 585 if ( "legacy".equals( repository.getLayout() ) ) 586 { 587 addViolation( problems, Severity.WARNING, Version.V20, prefix + ".layout", repository.getId(), 588 "uses the unsupported value 'legacy', artifact resolution might fail.", repository ); 589 } 590 } 591 } 592 593 private void validate20RawResources( ModelProblemCollector problems, List<Resource> resources, String prefix, 594 ModelBuildingRequest request ) 595 { 596 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 597 598 for ( Resource resource : resources ) 599 { 600 validateStringNotEmpty( prefix + ".directory", problems, Severity.ERROR, Version.V20, resource.getDirectory(), 601 resource ); 602 603 validateBoolean( prefix + ".filtering", problems, errOn30, Version.V20, resource.getFiltering(), 604 resource.getDirectory(), resource ); 605 } 606 } 607 608 // ---------------------------------------------------------------------- 609 // Field validation 610 // ---------------------------------------------------------------------- 611 612 private boolean validateId( String fieldName, ModelProblemCollector problems, String id, 613 InputLocationTracker tracker ) 614 { 615 return validateId( fieldName, problems, Severity.ERROR, Version.BASE, id, null, tracker ); 616 } 617 618 private boolean validateId( String fieldName, ModelProblemCollector problems, Severity severity, Version version, String id, 619 String sourceHint, InputLocationTracker tracker ) 620 { 621 if ( !validateStringNotEmpty( fieldName, problems, severity, version, id, sourceHint, tracker ) ) 622 { 623 return false; 624 } 625 else 626 { 627 boolean match = ID_REGEX.matcher( id ).matches(); 628 if ( !match ) 629 { 630 addViolation( problems, severity, version, fieldName, sourceHint, "with value '" + id 631 + "' does not match a valid id pattern.", tracker ); 632 } 633 return match; 634 } 635 } 636 637 private boolean validateStringNoExpression( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 638 String string, InputLocationTracker tracker ) 639 { 640 if ( !hasExpression( string ) ) 641 { 642 return true; 643 } 644 645 addViolation( problems, severity, version, fieldName, null, "contains an expression but should be a constant.", 646 tracker ); 647 648 return false; 649 } 650 651 private boolean hasExpression( String value ) 652 { 653 return value != null && value.contains( "${" ); 654 } 655 656 private boolean validateStringNotEmpty( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 657 String string, InputLocationTracker tracker ) 658 { 659 return validateStringNotEmpty( fieldName, problems, severity, version, string, null, tracker ); 660 } 661 662 /** 663 * Asserts: 664 * <p/> 665 * <ul> 666 * <li><code>string != null</code> 667 * <li><code>string.length > 0</code> 668 * </ul> 669 */ 670 private boolean validateStringNotEmpty( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 671 String string, String sourceHint, InputLocationTracker tracker ) 672 { 673 if ( !validateNotNull( fieldName, problems, severity, version, string, sourceHint, tracker ) ) 674 { 675 return false; 676 } 677 678 if ( string.length() > 0 ) 679 { 680 return true; 681 } 682 683 addViolation( problems, severity, version, fieldName, sourceHint, "is missing.", tracker ); 684 685 return false; 686 } 687 688 /** 689 * Asserts: 690 * <p/> 691 * <ul> 692 * <li><code>string != null</code> 693 * </ul> 694 */ 695 private boolean validateNotNull( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 696 Object object, String sourceHint, InputLocationTracker tracker ) 697 { 698 if ( object != null ) 699 { 700 return true; 701 } 702 703 addViolation( problems, severity, version, fieldName, sourceHint, "is missing.", tracker ); 704 705 return false; 706 } 707 708 private boolean validateBoolean( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 709 String string, String sourceHint, InputLocationTracker tracker ) 710 { 711 if ( string == null || string.length() <= 0 ) 712 { 713 return true; 714 } 715 716 if ( "true".equalsIgnoreCase( string ) || "false".equalsIgnoreCase( string ) ) 717 { 718 return true; 719 } 720 721 addViolation( problems, severity, version, fieldName, sourceHint, "must be 'true' or 'false' but is '" + string + "'.", 722 tracker ); 723 724 return false; 725 } 726 727 private boolean validateEnum( String fieldName, ModelProblemCollector problems, Severity severity, Version version, String string, 728 String sourceHint, InputLocationTracker tracker, String... validValues ) 729 { 730 if ( string == null || string.length() <= 0 ) 731 { 732 return true; 733 } 734 735 List<String> values = Arrays.asList( validValues ); 736 737 if ( values.contains( string ) ) 738 { 739 return true; 740 } 741 742 addViolation( problems, severity, version, fieldName, sourceHint, "must be one of " + values + " but is '" + string 743 + "'.", tracker ); 744 745 return false; 746 } 747 748 private boolean validateBannedCharacters( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 749 String string, String sourceHint, InputLocationTracker tracker, 750 String banned ) 751 { 752 if ( string != null ) 753 { 754 for ( int i = string.length() - 1; i >= 0; i-- ) 755 { 756 if ( banned.indexOf( string.charAt( i ) ) >= 0 ) 757 { 758 addViolation( problems, severity, version, fieldName, sourceHint, 759 "must not contain any of these characters " + banned + " but found " 760 + string.charAt( i ), tracker ); 761 return false; 762 } 763 } 764 } 765 766 return true; 767 } 768 769 private boolean validateVersion( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 770 String string, String sourceHint, InputLocationTracker tracker ) 771 { 772 if ( string == null || string.length() <= 0 ) 773 { 774 return true; 775 } 776 777 if ( hasExpression( string ) ) 778 { 779 addViolation( problems, severity, version, fieldName, sourceHint, 780 "must be a valid version but is '" + string + "'.", tracker ); 781 return false; 782 } 783 784 return validateBannedCharacters( fieldName, problems, severity, version, string, sourceHint, tracker, 785 ILLEGAL_VERSION_CHARS ); 786 787 } 788 789 private boolean validate20ProperSnapshotVersion( String fieldName, ModelProblemCollector problems, Severity severity, Version version, 790 String string, String sourceHint, InputLocationTracker tracker ) 791 { 792 if ( string == null || string.length() <= 0 ) 793 { 794 return true; 795 } 796 797 if ( string.endsWith( "SNAPSHOT" ) && !string.endsWith( "-SNAPSHOT" ) ) 798 { 799 addViolation( problems, severity, version, fieldName, sourceHint, "uses an unsupported snapshot version format" 800 + ", should be '*-SNAPSHOT' instead.", tracker ); 801 return false; 802 } 803 804 return true; 805 } 806 807 private boolean validate20PluginVersion( String fieldName, ModelProblemCollector problems, String string, 808 String sourceHint, InputLocationTracker tracker, 809 ModelBuildingRequest request ) 810 { 811 if ( string == null ) 812 { 813 // NOTE: The check for missing plugin versions is handled directly by the model builder 814 return true; 815 } 816 817 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 ); 818 819 if ( !validateVersion( fieldName, problems, errOn30, Version.V20, string, sourceHint, tracker ) ) 820 { 821 return false; 822 } 823 824 if ( string.length() <= 0 || "RELEASE".equals( string ) || "LATEST".equals( string ) ) 825 { 826 addViolation( problems, errOn30, Version.V20, fieldName, sourceHint, "must be a valid version but is '" + string + "'.", 827 tracker ); 828 return false; 829 } 830 831 return true; 832 } 833 834 private static void addViolation( ModelProblemCollector problems, Severity severity, Version version, String fieldName, 835 String sourceHint, String message, InputLocationTracker tracker ) 836 { 837 StringBuilder buffer = new StringBuilder( 256 ); 838 buffer.append( '\'' ).append( fieldName ).append( '\'' ); 839 840 if ( sourceHint != null ) 841 { 842 buffer.append( " for " ).append( sourceHint ); 843 } 844 845 buffer.append( ' ' ).append( message ); 846 847 problems.add( new ModelProblemCollectorRequest( severity, version ) 848 .setMessage( buffer.toString() ).setLocation( getLocation( fieldName, tracker ) ) ); 849 } 850 851 private static InputLocation getLocation( String fieldName, InputLocationTracker tracker ) 852 { 853 InputLocation location = null; 854 855 if ( tracker != null ) 856 { 857 if ( fieldName != null ) 858 { 859 Object key = fieldName; 860 861 int idx = fieldName.lastIndexOf( '.' ); 862 if ( idx >= 0 ) 863 { 864 fieldName = fieldName.substring( idx + 1 ); 865 key = fieldName; 866 } 867 868 if ( fieldName.endsWith( "]" ) ) 869 { 870 key = fieldName.substring( fieldName.lastIndexOf( '[' ) + 1, fieldName.length() - 1 ); 871 try 872 { 873 key = Integer.valueOf( key.toString() ); 874 } 875 catch ( NumberFormatException e ) 876 { 877 // use key as is 878 } 879 } 880 881 location = tracker.getLocation( key ); 882 } 883 884 if ( location == null ) 885 { 886 location = tracker.getLocation( "" ); 887 } 888 } 889 890 return location; 891 } 892 893 private static boolean equals( String s1, String s2 ) 894 { 895 return StringUtils.clean( s1 ).equals( StringUtils.clean( s2 ) ); 896 } 897 898 private static Severity getSeverity( ModelBuildingRequest request, int errorThreshold ) 899 { 900 return getSeverity( request.getValidationLevel(), errorThreshold ); 901 } 902 903 private static Severity getSeverity( int validationLevel, int errorThreshold ) 904 { 905 if ( validationLevel < errorThreshold ) 906 { 907 return Severity.WARNING; 908 } 909 else 910 { 911 return Severity.ERROR; 912 } 913 } 914 915}