View Javadoc
1   package org.apache.maven.project.inheritance;
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.util.ArrayList;
23  import java.util.LinkedHashMap;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.StringTokenizer;
29  import java.util.TreeMap;
30  
31  import org.apache.maven.model.Build;
32  import org.apache.maven.model.Dependency;
33  import org.apache.maven.model.DependencyManagement;
34  import org.apache.maven.model.DeploymentRepository;
35  import org.apache.maven.model.DistributionManagement;
36  import org.apache.maven.model.Extension;
37  import org.apache.maven.model.Model;
38  import org.apache.maven.model.PluginManagement;
39  import org.apache.maven.model.ReportPlugin;
40  import org.apache.maven.model.ReportSet;
41  import org.apache.maven.model.Reporting;
42  import org.apache.maven.model.Resource;
43  import org.apache.maven.model.Scm;
44  import org.apache.maven.model.Site;
45  import org.apache.maven.project.ModelUtils;
46  import org.codehaus.plexus.component.annotations.Component;
47  import org.codehaus.plexus.util.StringUtils;
48  import org.codehaus.plexus.util.xml.Xpp3Dom;
49  
50  /**
51   * DefaultModelInheritanceAssembler
52   */
53  @Component( role = ModelInheritanceAssembler.class )
54  public class DefaultModelInheritanceAssembler
55      implements ModelInheritanceAssembler
56  {
57      // TODO Remove this!
58      @SuppressWarnings( "unchecked" )
59      public void assembleBuildInheritance( Build childBuild, Build parentBuild, boolean handleAsInheritance )
60      {
61          // The build has been set but we want to step in here and fill in
62          // values that have not been set by the child.
63  
64          if ( childBuild.getSourceDirectory() == null )
65          {
66              childBuild.setSourceDirectory( parentBuild.getSourceDirectory() );
67          }
68  
69          if ( childBuild.getScriptSourceDirectory() == null )
70          {
71              childBuild.setScriptSourceDirectory( parentBuild.getScriptSourceDirectory() );
72          }
73  
74          if ( childBuild.getTestSourceDirectory() == null )
75          {
76              childBuild.setTestSourceDirectory( parentBuild.getTestSourceDirectory() );
77          }
78  
79          if ( childBuild.getOutputDirectory() == null )
80          {
81              childBuild.setOutputDirectory( parentBuild.getOutputDirectory() );
82          }
83  
84          if ( childBuild.getTestOutputDirectory() == null )
85          {
86              childBuild.setTestOutputDirectory( parentBuild.getTestOutputDirectory() );
87          }
88  
89          // Extensions are accumulated
90          mergeExtensionLists( childBuild, parentBuild );
91  
92          if ( childBuild.getDirectory() == null )
93          {
94              childBuild.setDirectory( parentBuild.getDirectory() );
95          }
96  
97          if ( childBuild.getDefaultGoal() == null )
98          {
99              childBuild.setDefaultGoal( parentBuild.getDefaultGoal() );
100         }
101 
102         if ( childBuild.getFinalName() == null )
103         {
104             childBuild.setFinalName( parentBuild.getFinalName() );
105         }
106 
107         ModelUtils.mergeFilterLists( childBuild.getFilters(), parentBuild.getFilters() );
108 
109         List<Resource> resources = childBuild.getResources();
110         if ( ( resources == null ) || resources.isEmpty() )
111         {
112             childBuild.setResources( parentBuild.getResources() );
113         }
114 
115         resources = childBuild.getTestResources();
116         if ( ( resources == null ) || resources.isEmpty() )
117         {
118             childBuild.setTestResources( parentBuild.getTestResources() );
119         }
120 
121         // Plugins are aggregated if Plugin.inherit != false
122         ModelUtils.mergePluginLists( childBuild, parentBuild, handleAsInheritance );
123 
124         // Plugin management :: aggregate
125         PluginManagement dominantPM = childBuild.getPluginManagement();
126         PluginManagement recessivePM = parentBuild.getPluginManagement();
127 
128         if ( ( dominantPM == null ) && ( recessivePM != null ) )
129         {
130             // FIXME: Filter out the inherited == false stuff!
131             childBuild.setPluginManagement( recessivePM );
132         }
133         else
134         {
135             ModelUtils.mergePluginLists( childBuild.getPluginManagement(), parentBuild.getPluginManagement(), false );
136         }
137     }
138 
139     private void assembleScmInheritance( Model child, Model parent, String childPathAdjustment, boolean appendPaths )
140     {
141         if ( parent.getScm() != null )
142         {
143             Scm parentScm = parent.getScm();
144 
145             Scm childScm = child.getScm();
146 
147             if ( childScm == null )
148             {
149                 childScm = new Scm();
150 
151                 child.setScm( childScm );
152             }
153 
154             if ( StringUtils.isEmpty( childScm.getConnection() ) && !StringUtils.isEmpty( parentScm.getConnection() ) )
155             {
156                 childScm.setConnection(
157                     appendPath( parentScm.getConnection(), child.getArtifactId(), childPathAdjustment, appendPaths ) );
158             }
159 
160             if ( StringUtils.isEmpty( childScm.getDeveloperConnection() )
161                 && !StringUtils.isEmpty( parentScm.getDeveloperConnection() ) )
162             {
163                 childScm
164                     .setDeveloperConnection( appendPath( parentScm.getDeveloperConnection(), child.getArtifactId(),
165                                                          childPathAdjustment, appendPaths ) );
166             }
167 
168             if ( StringUtils.isEmpty( childScm.getUrl() ) && !StringUtils.isEmpty( parentScm.getUrl() ) )
169             {
170                 childScm.setUrl(
171                     appendPath( parentScm.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths ) );
172             }
173         }
174     }
175 
176     public void copyModel( Model dest, Model source )
177     {
178         assembleModelInheritance( dest, source, null, false );
179     }
180 
181     public void assembleModelInheritance( Model child, Model parent, String childPathAdjustment )
182     {
183         assembleModelInheritance( child, parent, childPathAdjustment, true );
184     }
185 
186     public void assembleModelInheritance( Model child, Model parent )
187     {
188         assembleModelInheritance( child, parent, null, true );
189     }
190 
191     private void assembleModelInheritance( Model child, Model parent, String childPathAdjustment, boolean appendPaths )
192     {
193         // cannot inherit from null parent.
194         if ( parent == null )
195         {
196             return;
197         }
198 
199         // Group id
200         if ( child.getGroupId() == null )
201         {
202             child.setGroupId( parent.getGroupId() );
203         }
204 
205         // version
206         if ( child.getVersion() == null )
207         {
208             // The parent version may have resolved to something different, so we take what we asked for...
209             // instead of - child.setVersion( parent.getVersion() );
210 
211             if ( child.getParent() != null )
212             {
213                 child.setVersion( child.getParent().getVersion() );
214             }
215         }
216 
217         // inceptionYear
218         if ( child.getInceptionYear() == null )
219         {
220             child.setInceptionYear( parent.getInceptionYear() );
221         }
222 
223         // url
224         if ( child.getUrl() == null )
225         {
226             if ( parent.getUrl() != null )
227             {
228                 child.setUrl( appendPath( parent.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths ) );
229             }
230             else
231             {
232                 child.setUrl( parent.getUrl() );
233             }
234         }
235 
236         assembleDistributionInheritance( child, parent, childPathAdjustment, appendPaths );
237 
238         // issueManagement
239         if ( child.getIssueManagement() == null )
240         {
241             child.setIssueManagement( parent.getIssueManagement() );
242         }
243 
244         // description
245         if ( child.getDescription() == null )
246         {
247             child.setDescription( parent.getDescription() );
248         }
249 
250         // Organization
251         if ( child.getOrganization() == null )
252         {
253             child.setOrganization( parent.getOrganization() );
254         }
255 
256         // Scm
257         assembleScmInheritance( child, parent, childPathAdjustment, appendPaths );
258 
259         // ciManagement
260         if ( child.getCiManagement() == null )
261         {
262             child.setCiManagement( parent.getCiManagement() );
263         }
264 
265         // developers
266         if ( child.getDevelopers().size() == 0 )
267         {
268             child.setDevelopers( parent.getDevelopers() );
269         }
270 
271         // licenses
272         if ( child.getLicenses().size() == 0 )
273         {
274             child.setLicenses( parent.getLicenses() );
275         }
276 
277         // developers
278         if ( child.getContributors().size() == 0 )
279         {
280             child.setContributors( parent.getContributors() );
281         }
282 
283         // mailingLists
284         if ( child.getMailingLists().size() == 0 )
285         {
286             child.setMailingLists( parent.getMailingLists() );
287         }
288 
289         // Build
290         assembleBuildInheritance( child, parent );
291 
292         assembleDependencyInheritance( child, parent );
293 
294         child.setRepositories( ModelUtils.mergeRepositoryLists( child.getRepositories(), parent.getRepositories() ) );
295 //        child.setPluginRepositories(
296 //            ModelUtils.mergeRepositoryLists( child.getPluginRepositories(), parent.getPluginRepositories() ) );
297 
298         assembleReportingInheritance( child, parent );
299 
300         assembleDependencyManagementInheritance( child, parent );
301 
302         Properties props = new Properties();
303         props.putAll( parent.getProperties() );
304         props.putAll( child.getProperties() );
305 
306         child.setProperties( props );
307     }
308 
309     // TODO Remove this!
310     @SuppressWarnings( "unchecked" )
311     private void assembleDependencyManagementInheritance( Model child, Model parent )
312     {
313         DependencyManagement parentDepMgmt = parent.getDependencyManagement();
314 
315         DependencyManagement childDepMgmt = child.getDependencyManagement();
316 
317         if ( parentDepMgmt != null )
318         {
319             if ( childDepMgmt == null )
320             {
321                 child.setDependencyManagement( parentDepMgmt );
322             }
323             else
324             {
325                 List<Dependency> childDeps = childDepMgmt.getDependencies();
326 
327                 Map<String, Dependency> mappedChildDeps = new TreeMap<>();
328                 for ( Dependency dep : childDeps )
329                 {
330                     mappedChildDeps.put( dep.getManagementKey(), dep );
331                 }
332 
333                 for ( Dependency dep : parentDepMgmt.getDependencies() )
334                 {
335                     if ( !mappedChildDeps.containsKey( dep.getManagementKey() ) )
336                     {
337                         childDepMgmt.addDependency( dep );
338                     }
339                 }
340             }
341         }
342     }
343 
344     private void assembleReportingInheritance( Model child, Model parent )
345     {
346         // Reports :: aggregate
347         Reporting childReporting = child.getReporting();
348         Reporting parentReporting = parent.getReporting();
349 
350         if ( parentReporting != null )
351         {
352             if ( childReporting == null )
353             {
354                 childReporting = new Reporting();
355                 child.setReporting( childReporting );
356             }
357 
358             childReporting.setExcludeDefaults( parentReporting.isExcludeDefaults() );
359 
360             if ( StringUtils.isEmpty( childReporting.getOutputDirectory() ) )
361             {
362                 childReporting.setOutputDirectory( parentReporting.getOutputDirectory() );
363             }
364 
365             mergeReportPluginLists( childReporting, parentReporting, true );
366         }
367     }
368 
369     private static void mergeReportPluginLists( Reporting child, Reporting parent, boolean handleAsInheritance )
370     {
371         if ( ( child == null ) || ( parent == null ) )
372         {
373             // nothing to do.
374             return;
375         }
376 
377         List<ReportPlugin> parentPlugins = parent.getPlugins();
378 
379         if ( ( parentPlugins != null ) && !parentPlugins.isEmpty() )
380         {
381             Map<String, ReportPlugin> assembledPlugins = new TreeMap<>();
382 
383             Map<String, ReportPlugin> childPlugins = child.getReportPluginsAsMap();
384 
385             for ( ReportPlugin parentPlugin : parentPlugins )
386             {
387                 String parentInherited = parentPlugin.getInherited();
388 
389                 if ( !handleAsInheritance || ( parentInherited == null ) || Boolean.valueOf( parentInherited ) )
390                 {
391 
392                     ReportPlugin assembledPlugin = parentPlugin;
393 
394                     ReportPlugin childPlugin = childPlugins.get( parentPlugin.getKey() );
395 
396                     if ( childPlugin != null )
397                     {
398                         assembledPlugin = childPlugin;
399 
400                         mergeReportPluginDefinitions( childPlugin, parentPlugin, handleAsInheritance );
401                     }
402 
403                     if ( handleAsInheritance && ( parentInherited == null ) )
404                     {
405                         assembledPlugin.unsetInheritanceApplied();
406                     }
407 
408                     assembledPlugins.put( assembledPlugin.getKey(), assembledPlugin );
409                 }
410             }
411 
412             for ( ReportPlugin childPlugin : childPlugins.values() )
413             {
414                 if ( !assembledPlugins.containsKey( childPlugin.getKey() ) )
415                 {
416                     assembledPlugins.put( childPlugin.getKey(), childPlugin );
417                 }
418             }
419 
420             child.setPlugins( new ArrayList<>( assembledPlugins.values() ) );
421 
422             child.flushReportPluginMap();
423         }
424     }
425 
426     private static void mergeReportSetDefinitions( ReportSet child, ReportSet parent )
427     {
428         List<String> parentReports = parent.getReports();
429         List<String> childReports = child.getReports();
430 
431         List<String> reports = new ArrayList<>();
432 
433         if ( ( childReports != null ) && !childReports.isEmpty() )
434         {
435             reports.addAll( childReports );
436         }
437 
438         if ( parentReports != null )
439         {
440             for ( String report : parentReports )
441             {
442                 if ( !reports.contains( report ) )
443                 {
444                     reports.add( report );
445                 }
446             }
447         }
448 
449         child.setReports( reports );
450 
451         Xpp3Dom childConfiguration = (Xpp3Dom) child.getConfiguration();
452         Xpp3Dom parentConfiguration = (Xpp3Dom) parent.getConfiguration();
453 
454         childConfiguration = Xpp3Dom.mergeXpp3Dom( childConfiguration, parentConfiguration );
455 
456         child.setConfiguration( childConfiguration );
457     }
458 
459 
460     public static void mergeReportPluginDefinitions( ReportPlugin child, ReportPlugin parent,
461                                                      boolean handleAsInheritance )
462     {
463         if ( ( child == null ) || ( parent == null ) )
464         {
465             // nothing to do.
466             return;
467         }
468 
469         if ( ( child.getVersion() == null ) && ( parent.getVersion() != null ) )
470         {
471             child.setVersion( parent.getVersion() );
472         }
473 
474         // from here to the end of the method is dealing with merging of the <executions/> section.
475         String parentInherited = parent.getInherited();
476 
477         boolean parentIsInherited = ( parentInherited == null ) || Boolean.valueOf( parentInherited );
478 
479         List<ReportSet> parentReportSets = parent.getReportSets();
480 
481         if ( ( parentReportSets != null ) && !parentReportSets.isEmpty() )
482         {
483             Map<String, ReportSet> assembledReportSets = new TreeMap<>();
484 
485             Map<String, ReportSet> childReportSets = child.getReportSetsAsMap();
486 
487             for ( Object parentReportSet1 : parentReportSets )
488             {
489                 ReportSet parentReportSet = (ReportSet) parentReportSet1;
490 
491                 if ( !handleAsInheritance || parentIsInherited )
492                 {
493                     ReportSet assembledReportSet = parentReportSet;
494 
495                     ReportSet childReportSet = childReportSets.get( parentReportSet.getId() );
496 
497                     if ( childReportSet != null )
498                     {
499                         mergeReportSetDefinitions( childReportSet, parentReportSet );
500 
501                         assembledReportSet = childReportSet;
502                     }
503                     else if ( handleAsInheritance && ( parentInherited == null ) )
504                     {
505                         parentReportSet.unsetInheritanceApplied();
506                     }
507 
508                     assembledReportSets.put( assembledReportSet.getId(), assembledReportSet );
509                 }
510             }
511 
512             for ( Map.Entry<String, ReportSet> entry : childReportSets.entrySet() )
513             {
514                 String id = entry.getKey();
515 
516                 if ( !assembledReportSets.containsKey( id ) )
517                 {
518                     assembledReportSets.put( id, entry.getValue() );
519                 }
520             }
521 
522             child.setReportSets( new ArrayList<>( assembledReportSets.values() ) );
523 
524             child.flushReportSetMap();
525         }
526 
527     }
528 
529     // TODO Remove this!
530     @SuppressWarnings( "unchecked" )
531     private void assembleDependencyInheritance( Model child, Model parent )
532     {
533         Map<String, Dependency> depsMap = new LinkedHashMap<>();
534 
535         List<Dependency> deps = parent.getDependencies();
536 
537         if ( deps != null )
538         {
539             for ( Dependency dependency : deps )
540             {
541                 depsMap.put( dependency.getManagementKey(), dependency );
542             }
543         }
544 
545         deps = child.getDependencies();
546 
547         if ( deps != null )
548         {
549             for ( Dependency dependency : deps )
550             {
551                 depsMap.put( dependency.getManagementKey(), dependency );
552             }
553         }
554 
555         child.setDependencies( new ArrayList<>( depsMap.values() ) );
556     }
557 
558     private void assembleBuildInheritance( Model child, Model parent )
559     {
560         Build childBuild = child.getBuild();
561         Build parentBuild = parent.getBuild();
562 
563         if ( parentBuild != null )
564         {
565             if ( childBuild == null )
566             {
567                 childBuild = new Build();
568                 child.setBuild( childBuild );
569             }
570 
571             assembleBuildInheritance( childBuild, parentBuild, true );
572         }
573     }
574 
575     private void assembleDistributionInheritance( Model child, Model parent, String childPathAdjustment,
576                                                   boolean appendPaths )
577     {
578         if ( parent.getDistributionManagement() != null )
579         {
580             DistributionManagement parentDistMgmt = parent.getDistributionManagement();
581 
582             DistributionManagement childDistMgmt = child.getDistributionManagement();
583 
584             if ( childDistMgmt == null )
585             {
586                 childDistMgmt = new DistributionManagement();
587 
588                 child.setDistributionManagement( childDistMgmt );
589             }
590 
591             if ( childDistMgmt.getSite() == null )
592             {
593                 if ( parentDistMgmt.getSite() != null )
594                 {
595                     Site site = new Site();
596 
597                     childDistMgmt.setSite( site );
598 
599                     site.setId( parentDistMgmt.getSite().getId() );
600 
601                     site.setName( parentDistMgmt.getSite().getName() );
602 
603                     site.setUrl( parentDistMgmt.getSite().getUrl() );
604 
605                     if ( site.getUrl() != null )
606                     {
607                         site.setUrl(
608                             appendPath( site.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths ) );
609                     }
610                 }
611             }
612 
613             if ( childDistMgmt.getRepository() == null )
614             {
615                 if ( parentDistMgmt.getRepository() != null )
616                 {
617                     DeploymentRepository repository = copyDistributionRepository( parentDistMgmt.getRepository() );
618                     childDistMgmt.setRepository( repository );
619                 }
620             }
621 
622             if ( childDistMgmt.getSnapshotRepository() == null )
623             {
624                 if ( parentDistMgmt.getSnapshotRepository() != null )
625                 {
626                     DeploymentRepository repository =
627                         copyDistributionRepository( parentDistMgmt.getSnapshotRepository() );
628                     childDistMgmt.setSnapshotRepository( repository );
629                 }
630             }
631 
632             if ( StringUtils.isEmpty( childDistMgmt.getDownloadUrl() ) )
633             {
634                 childDistMgmt.setDownloadUrl( parentDistMgmt.getDownloadUrl() );
635             }
636 
637             // NOTE: We SHOULD NOT be inheriting status, since this is an assessment of the POM quality.
638             // NOTE: We SHOULD NOT be inheriting relocation, since this relates to a single POM
639         }
640     }
641 
642     private static DeploymentRepository copyDistributionRepository( DeploymentRepository parentRepository )
643     {
644         DeploymentRepository repository = new DeploymentRepository();
645 
646         repository.setId( parentRepository.getId() );
647 
648         repository.setName( parentRepository.getName() );
649 
650         repository.setUrl( parentRepository.getUrl() );
651 
652         repository.setLayout( parentRepository.getLayout() );
653 
654         repository.setUniqueVersion( parentRepository.isUniqueVersion() );
655 
656         return repository;
657     }
658 
659     // TODO This should eventually be migrated to DefaultPathTranslator.
660     protected String appendPath( String parentPath, String childPath, String pathAdjustment, boolean appendPaths )
661     {
662         String uncleanPath = parentPath;
663 
664         if ( appendPaths )
665         {
666             if ( pathAdjustment != null )
667             {
668                 uncleanPath += "/" + pathAdjustment;
669             }
670 
671             if ( childPath != null )
672             {
673                 uncleanPath += "/" + childPath;
674             }
675         }
676 
677         String cleanedPath = "";
678 
679         int protocolIdx = uncleanPath.indexOf( "://" );
680 
681         if ( protocolIdx > -1 )
682         {
683             cleanedPath = uncleanPath.substring( 0, protocolIdx + 3 );
684             uncleanPath = uncleanPath.substring( protocolIdx + 3 );
685         }
686 
687         if ( uncleanPath.startsWith( "/" ) )
688         {
689             cleanedPath += "/";
690         }
691 
692         return cleanedPath + resolvePath( uncleanPath );
693     }
694 
695     // TODO Move this to plexus-utils' PathTool.
696     private static String resolvePath( String uncleanPath )
697     {
698         LinkedList<String> pathElements = new LinkedList<>();
699 
700         StringTokenizer tokenizer = new StringTokenizer( uncleanPath, "/" );
701 
702         while ( tokenizer.hasMoreTokens() )
703         {
704             String token = tokenizer.nextToken();
705 
706             switch ( token )
707             {
708                 case "":
709                     // Empty path entry ("...//.."), remove.
710                     break;
711                 case "..":
712                     if ( pathElements.isEmpty() )
713                     {
714                         // FIXME: somehow report to the user
715                         // that there are too many '..' elements.
716                         // For now, ignore the extra '..'.
717                     }
718                     else
719                     {
720                         pathElements.removeLast();
721                     }
722                     break;
723                 default:
724                     pathElements.addLast( token );
725                     break;
726             }
727         }
728 
729         StringBuilder cleanedPath = new StringBuilder();
730 
731         while ( !pathElements.isEmpty() )
732         {
733             cleanedPath.append( pathElements.removeFirst() );
734             if ( !pathElements.isEmpty() )
735             {
736                 cleanedPath.append( '/' );
737             }
738         }
739 
740         return cleanedPath.toString();
741     }
742 
743     private static void mergeExtensionLists( Build childBuild, Build parentBuild )
744     {
745         for ( Extension e : parentBuild.getExtensions() )
746         {
747             if ( !childBuild.getExtensions().contains( e ) )
748             {
749                 childBuild.addExtension( e );
750             }
751         }
752     }
753 }