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