View Javadoc

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