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