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