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