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