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