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  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   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
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                      * TODO: Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc.
441                      * In order to don't break backward-compat with those, only warn but don't error out.
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     // Field validation
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      * Asserts:
664      * <p/>
665      * <ul>
666      * <li><code>string != null</code>
667      * <li><code>string.length > 0</code>
668      * </ul>
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      * Asserts:
690      * <p/>
691      * <ul>
692      * <li><code>string != null</code>
693      * </ul>
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             // NOTE: The check for missing plugin versions is handled directly by the model builder
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                         // use key as is
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 }