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