1 package org.apache.maven.model.validation;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.regex.Pattern;
30
31 import org.apache.maven.model.Activation;
32 import org.apache.maven.model.ActivationFile;
33 import org.apache.maven.model.Build;
34 import org.apache.maven.model.BuildBase;
35 import org.apache.maven.model.Dependency;
36 import org.apache.maven.model.DependencyManagement;
37 import org.apache.maven.model.DistributionManagement;
38 import org.apache.maven.model.Exclusion;
39 import org.apache.maven.model.InputLocation;
40 import org.apache.maven.model.InputLocationTracker;
41 import org.apache.maven.model.Model;
42 import org.apache.maven.model.Parent;
43 import org.apache.maven.model.Plugin;
44 import org.apache.maven.model.PluginExecution;
45 import org.apache.maven.model.PluginManagement;
46 import org.apache.maven.model.Profile;
47 import org.apache.maven.model.ReportPlugin;
48 import org.apache.maven.model.Reporting;
49 import org.apache.maven.model.Repository;
50 import org.apache.maven.model.Resource;
51 import org.apache.maven.model.building.ModelBuildingRequest;
52 import org.apache.maven.model.building.ModelProblem.Severity;
53 import org.apache.maven.model.building.ModelProblem.Version;
54 import org.apache.maven.model.building.ModelProblemCollector;
55 import org.apache.maven.model.building.ModelProblemCollectorRequest;
56 import org.codehaus.plexus.component.annotations.Component;
57 import org.codehaus.plexus.util.StringUtils;
58
59
60
61
62 @Component( role = ModelValidator.class )
63 public class DefaultModelValidator
64 implements ModelValidator
65 {
66
67 private static final Pattern ID_REGEX = Pattern.compile( "[A-Za-z0-9_\\-.]+" );
68
69 private static final Pattern ID_WITH_WILDCARDS_REGEX = Pattern.compile( "[A-Za-z0-9_\\-.?*]+" );
70
71 private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*";
72
73 private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS;
74
75 private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS;
76
77 public void validateRawModel( Model m, ModelBuildingRequest request, ModelProblemCollector problems )
78 {
79 Parent parent = m.getParent();
80 if ( parent != null )
81 {
82 validateStringNotEmpty( "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(),
83 parent );
84
85 validateStringNotEmpty( "parent.artifactId", problems, Severity.FATAL, Version.BASE,
86 parent.getArtifactId(), parent );
87
88 validateStringNotEmpty( "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(),
89 parent );
90
91 if ( equals( parent.getGroupId(), m.getGroupId() )
92 && equals( parent.getArtifactId(), m.getArtifactId() ) )
93 {
94 addViolation( problems, Severity.FATAL, Version.BASE, "parent.artifactId", null, "must be changed"
95 + ", the parent element cannot have the same groupId:artifactId as the project.", parent );
96 }
97 }
98
99 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
515
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
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
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
784
785
786
787
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
819
820
821
822
823
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
846
847
848
849
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
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
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 }