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.Collection;
024import java.util.Iterator;
025import java.util.LinkedHashMap;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Properties;
030import java.util.StringTokenizer;
031import java.util.TreeMap;
032
033import org.apache.maven.model.Build;
034import org.apache.maven.model.Dependency;
035import org.apache.maven.model.DependencyManagement;
036import org.apache.maven.model.DeploymentRepository;
037import org.apache.maven.model.DistributionManagement;
038import org.apache.maven.model.Extension;
039import org.apache.maven.model.Model;
040import org.apache.maven.model.PluginManagement;
041import org.apache.maven.model.ReportPlugin;
042import org.apache.maven.model.ReportSet;
043import org.apache.maven.model.Reporting;
044import org.apache.maven.model.Resource;
045import org.apache.maven.model.Scm;
046import org.apache.maven.model.Site;
047import org.apache.maven.project.ModelUtils;
048import org.codehaus.plexus.component.annotations.Component;
049import org.codehaus.plexus.util.StringUtils;
050import org.codehaus.plexus.util.xml.Xpp3Dom;
051
052@Component( role = ModelInheritanceAssembler.class )
053public class DefaultModelInheritanceAssembler
054    implements ModelInheritanceAssembler
055{
056    // TODO: Remove this!
057    @SuppressWarnings( "unchecked" )
058    public void assembleBuildInheritance( Build childBuild, Build parentBuild, boolean handleAsInheritance )
059    {
060        // The build has been set but we want to step in here and fill in
061        // values that have not been set by the child.
062
063        if ( childBuild.getSourceDirectory() == null )
064        {
065            childBuild.setSourceDirectory( parentBuild.getSourceDirectory() );
066        }
067
068        if ( childBuild.getScriptSourceDirectory() == null )
069        {
070            childBuild.setScriptSourceDirectory( parentBuild.getScriptSourceDirectory() );
071        }
072
073        if ( childBuild.getTestSourceDirectory() == null )
074        {
075            childBuild.setTestSourceDirectory( parentBuild.getTestSourceDirectory() );
076        }
077
078        if ( childBuild.getOutputDirectory() == null )
079        {
080            childBuild.setOutputDirectory( parentBuild.getOutputDirectory() );
081        }
082
083        if ( childBuild.getTestOutputDirectory() == null )
084        {
085            childBuild.setTestOutputDirectory( parentBuild.getTestOutputDirectory() );
086        }
087
088        // Extensions are accumulated
089        mergeExtensionLists( childBuild, parentBuild );
090
091        if ( childBuild.getDirectory() == null )
092        {
093            childBuild.setDirectory( parentBuild.getDirectory() );
094        }
095
096        if ( childBuild.getDefaultGoal() == null )
097        {
098            childBuild.setDefaultGoal( parentBuild.getDefaultGoal() );
099        }
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}