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