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