001package org.apache.maven.project.inheritance;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.ArrayList;
023import java.util.LinkedHashMap;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.Properties;
028import java.util.StringTokenizer;
029import java.util.TreeMap;
030
031import org.apache.maven.model.Build;
032import org.apache.maven.model.Dependency;
033import org.apache.maven.model.DependencyManagement;
034import org.apache.maven.model.DeploymentRepository;
035import org.apache.maven.model.DistributionManagement;
036import org.apache.maven.model.Extension;
037import org.apache.maven.model.Model;
038import org.apache.maven.model.PluginManagement;
039import org.apache.maven.model.ReportPlugin;
040import org.apache.maven.model.ReportSet;
041import org.apache.maven.model.Reporting;
042import org.apache.maven.model.Resource;
043import org.apache.maven.model.Scm;
044import org.apache.maven.model.Site;
045import org.apache.maven.project.ModelUtils;
046import org.codehaus.plexus.component.annotations.Component;
047import org.codehaus.plexus.util.StringUtils;
048import org.codehaus.plexus.util.xml.Xpp3Dom;
049
050@Component( role = ModelInheritanceAssembler.class )
051public class DefaultModelInheritanceAssembler
052    implements ModelInheritanceAssembler
053{
054    // TODO: Remove this!
055    @SuppressWarnings( "unchecked" )
056    public void assembleBuildInheritance( Build childBuild, Build parentBuild, boolean handleAsInheritance )
057    {
058        // The build has been set but we want to step in here and fill in
059        // values that have not been set by the child.
060
061        if ( childBuild.getSourceDirectory() == null )
062        {
063            childBuild.setSourceDirectory( parentBuild.getSourceDirectory() );
064        }
065
066        if ( childBuild.getScriptSourceDirectory() == null )
067        {
068            childBuild.setScriptSourceDirectory( parentBuild.getScriptSourceDirectory() );
069        }
070
071        if ( childBuild.getTestSourceDirectory() == null )
072        {
073            childBuild.setTestSourceDirectory( parentBuild.getTestSourceDirectory() );
074        }
075
076        if ( childBuild.getOutputDirectory() == null )
077        {
078            childBuild.setOutputDirectory( parentBuild.getOutputDirectory() );
079        }
080
081        if ( childBuild.getTestOutputDirectory() == null )
082        {
083            childBuild.setTestOutputDirectory( parentBuild.getTestOutputDirectory() );
084        }
085
086        // Extensions are accumulated
087        mergeExtensionLists( childBuild, parentBuild );
088
089        if ( childBuild.getDirectory() == null )
090        {
091            childBuild.setDirectory( parentBuild.getDirectory() );
092        }
093
094        if ( childBuild.getDefaultGoal() == null )
095        {
096            childBuild.setDefaultGoal( parentBuild.getDefaultGoal() );
097        }
098
099        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}