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 @Override
78 public void validateRawModel( Model m, ModelBuildingRequest request, ModelProblemCollector problems )
79 {
80 Parent parent = m.getParent();
81 if ( parent != null )
82 {
83 validateStringNotEmpty( "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(),
84 parent );
85
86 validateStringNotEmpty( "parent.artifactId", problems, Severity.FATAL, Version.BASE,
87 parent.getArtifactId(), parent );
88
89 validateStringNotEmpty( "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(),
90 parent );
91
92 if ( equals( parent.getGroupId(), m.getGroupId() )
93 && equals( parent.getArtifactId(), m.getArtifactId() ) )
94 {
95 addViolation( problems, Severity.FATAL, Version.BASE, "parent.artifactId", null, "must be changed"
96 + ", the parent element cannot have the same groupId:artifactId as the project.", parent );
97 }
98 }
99
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<>();
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<>();
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<>();
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<>();
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<>();
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
517
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
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<>();
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
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
786
787
788
789
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
821
822
823
824
825
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
848
849
850
851
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
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
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 }