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