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