001    package 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    
022    import java.util.ArrayList;
023    import java.util.Iterator;
024    import java.util.LinkedHashMap;
025    import java.util.LinkedList;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Properties;
029    import java.util.StringTokenizer;
030    import java.util.TreeMap;
031    
032    import org.apache.maven.model.Build;
033    import org.apache.maven.model.Dependency;
034    import org.apache.maven.model.DependencyManagement;
035    import org.apache.maven.model.DeploymentRepository;
036    import org.apache.maven.model.DistributionManagement;
037    import org.apache.maven.model.Extension;
038    import org.apache.maven.model.Model;
039    import org.apache.maven.model.PluginManagement;
040    import org.apache.maven.model.ReportPlugin;
041    import org.apache.maven.model.ReportSet;
042    import org.apache.maven.model.Reporting;
043    import org.apache.maven.model.Resource;
044    import org.apache.maven.model.Scm;
045    import org.apache.maven.model.Site;
046    import org.apache.maven.project.ModelUtils;
047    import org.codehaus.plexus.component.annotations.Component;
048    import org.codehaus.plexus.util.StringUtils;
049    import org.codehaus.plexus.util.xml.Xpp3Dom;
050    
051    @Component( role = ModelInheritanceAssembler.class )
052    public 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 ( Iterator<Dependency> it = childDeps.iterator(); it.hasNext(); )
327                    {
328                        Dependency dep = it.next();
329                        mappedChildDeps.put( dep.getManagementKey(), dep );
330                    }
331    
332                    for ( Iterator<Dependency> it = parentDepMgmt.getDependencies().iterator(); it.hasNext(); )
333                    {
334                        Dependency dep = it.next();
335                        if ( !mappedChildDeps.containsKey( dep.getManagementKey() ) )
336                        {
337                            childDepMgmt.addDependency( dep );
338                        }
339                    }
340                }
341            }
342        }
343    
344        private void assembleReportingInheritance( Model child, Model parent )
345        {
346            // Reports :: aggregate
347            Reporting childReporting = child.getReporting();
348            Reporting parentReporting = parent.getReporting();
349    
350            if ( parentReporting != null )
351            {
352                if ( childReporting == null )
353                {
354                    childReporting = new Reporting();
355                    child.setReporting( childReporting );
356                }
357    
358                childReporting.setExcludeDefaults( parentReporting.isExcludeDefaults() );
359    
360                if ( StringUtils.isEmpty( childReporting.getOutputDirectory() ) )
361                {
362                    childReporting.setOutputDirectory( parentReporting.getOutputDirectory() );
363                }
364    
365                mergeReportPluginLists( childReporting, parentReporting, true );
366            }
367        }
368    
369        private static void mergeReportPluginLists( Reporting child, Reporting parent, boolean handleAsInheritance )
370        {
371            if ( ( child == null ) || ( parent == null ) )
372            {
373                // nothing to do.
374                return;
375            }
376    
377            List parentPlugins = parent.getPlugins();
378    
379            if ( ( parentPlugins != null ) && !parentPlugins.isEmpty() )
380            {
381                Map assembledPlugins = new TreeMap();
382    
383                Map childPlugins = child.getReportPluginsAsMap();
384    
385                for ( Iterator it = parentPlugins.iterator(); it.hasNext(); )
386                {
387                    ReportPlugin parentPlugin = (ReportPlugin) it.next();
388    
389                    String parentInherited = parentPlugin.getInherited();
390    
391                    if ( !handleAsInheritance || ( parentInherited == null )
392                        || Boolean.valueOf( parentInherited ).booleanValue() )
393                    {
394    
395                        ReportPlugin assembledPlugin = parentPlugin;
396    
397                        ReportPlugin childPlugin = (ReportPlugin) childPlugins.get( parentPlugin.getKey() );
398    
399                        if ( childPlugin != null )
400                        {
401                            assembledPlugin = childPlugin;
402    
403                            mergeReportPluginDefinitions( childPlugin, parentPlugin, handleAsInheritance );
404                        }
405    
406                        if ( handleAsInheritance && ( parentInherited == null ) )
407                        {
408                            assembledPlugin.unsetInheritanceApplied();
409                        }
410    
411                        assembledPlugins.put( assembledPlugin.getKey(), assembledPlugin );
412                    }
413                }
414    
415                for ( Iterator it = childPlugins.values().iterator(); it.hasNext(); )
416                {
417                    ReportPlugin childPlugin = (ReportPlugin) it.next();
418    
419                    if ( !assembledPlugins.containsKey( childPlugin.getKey() ) )
420                    {
421                        assembledPlugins.put( childPlugin.getKey(), childPlugin );
422                    }
423                }
424    
425                child.setPlugins( new ArrayList( assembledPlugins.values() ) );
426    
427                child.flushReportPluginMap();
428            }
429        }
430    
431        private static void mergeReportSetDefinitions( ReportSet child, ReportSet parent )
432        {
433            List parentReports = parent.getReports();
434            List childReports = child.getReports();
435    
436            List reports = new ArrayList();
437    
438            if ( ( childReports != null ) && !childReports.isEmpty() )
439            {
440                reports.addAll( childReports );
441            }
442    
443            if ( parentReports != null )
444            {
445                for ( Iterator i = parentReports.iterator(); i.hasNext(); )
446                {
447                    String report = (String) i.next();
448    
449                    if ( !reports.contains( report ) )
450                    {
451                        reports.add( report );
452                    }
453                }
454            }
455    
456            child.setReports( reports );
457    
458            Xpp3Dom childConfiguration = (Xpp3Dom) child.getConfiguration();
459            Xpp3Dom parentConfiguration = (Xpp3Dom) parent.getConfiguration();
460    
461            childConfiguration = Xpp3Dom.mergeXpp3Dom( childConfiguration, parentConfiguration );
462    
463            child.setConfiguration( childConfiguration );
464        }
465    
466    
467        public static void mergeReportPluginDefinitions( ReportPlugin child, ReportPlugin parent,
468                                                         boolean handleAsInheritance )
469        {
470            if ( ( child == null ) || ( parent == null ) )
471            {
472                // nothing to do.
473                return;
474            }
475    
476            if ( ( child.getVersion() == null ) && ( parent.getVersion() != null ) )
477            {
478                child.setVersion( parent.getVersion() );
479            }
480    
481            // from here to the end of the method is dealing with merging of the <executions/> section.
482            String parentInherited = parent.getInherited();
483    
484            boolean parentIsInherited = ( parentInherited == null ) || Boolean.valueOf( parentInherited ).booleanValue();
485    
486            List parentReportSets = parent.getReportSets();
487    
488            if ( ( parentReportSets != null ) && !parentReportSets.isEmpty() )
489            {
490                Map assembledReportSets = new TreeMap();
491    
492                Map childReportSets = child.getReportSetsAsMap();
493    
494                for ( Iterator it = parentReportSets.iterator(); it.hasNext(); )
495                {
496                    ReportSet parentReportSet = (ReportSet) it.next();
497    
498                    if ( !handleAsInheritance || parentIsInherited )
499                    {
500                        ReportSet assembledReportSet = parentReportSet;
501    
502                        ReportSet childReportSet = (ReportSet) childReportSets.get( parentReportSet.getId() );
503    
504                        if ( childReportSet != null )
505                        {
506                            mergeReportSetDefinitions( childReportSet, parentReportSet );
507    
508                            assembledReportSet = childReportSet;
509                        }
510                        else if ( handleAsInheritance && ( parentInherited == null ) )
511                        {
512                            parentReportSet.unsetInheritanceApplied();
513                        }
514    
515                        assembledReportSets.put( assembledReportSet.getId(), assembledReportSet );
516                    }
517                }
518    
519                for ( Iterator it = childReportSets.entrySet().iterator(); it.hasNext(); )
520                {
521                    Map.Entry entry = (Map.Entry) it.next();
522    
523                    String id = (String) entry.getKey();
524    
525                    if ( !assembledReportSets.containsKey( id ) )
526                    {
527                        assembledReportSets.put( id, entry.getValue() );
528                    }
529                }
530    
531                child.setReportSets( new ArrayList( assembledReportSets.values() ) );
532    
533                child.flushReportSetMap();
534            }
535    
536        }
537    
538        // TODO: Remove this!
539        @SuppressWarnings( "unchecked" )
540        private void assembleDependencyInheritance( Model child, Model parent )
541        {
542            Map<String, Dependency> depsMap = new LinkedHashMap<String, Dependency>();
543    
544            List<Dependency> deps = parent.getDependencies();
545    
546            if ( deps != null )
547            {
548                for ( Dependency dependency : deps )
549                {
550                    depsMap.put( dependency.getManagementKey(), dependency );
551                }
552            }
553    
554            deps = child.getDependencies();
555    
556            if ( deps != null )
557            {
558                for ( Dependency dependency : deps )
559                {
560                    depsMap.put( dependency.getManagementKey(), dependency );
561                }
562            }
563    
564            child.setDependencies( new ArrayList<Dependency>( depsMap.values() ) );
565        }
566    
567        private void assembleBuildInheritance( Model child, Model parent )
568        {
569            Build childBuild = child.getBuild();
570            Build parentBuild = parent.getBuild();
571    
572            if ( parentBuild != null )
573            {
574                if ( childBuild == null )
575                {
576                    childBuild = new Build();
577                    child.setBuild( childBuild );
578                }
579    
580                assembleBuildInheritance( childBuild, parentBuild, true );
581            }
582        }
583    
584        private void assembleDistributionInheritence( Model child, Model parent, String childPathAdjustment,
585                                                      boolean appendPaths )
586        {
587            if ( parent.getDistributionManagement() != null )
588            {
589                DistributionManagement parentDistMgmt = parent.getDistributionManagement();
590    
591                DistributionManagement childDistMgmt = child.getDistributionManagement();
592    
593                if ( childDistMgmt == null )
594                {
595                    childDistMgmt = new DistributionManagement();
596    
597                    child.setDistributionManagement( childDistMgmt );
598                }
599    
600                if ( childDistMgmt.getSite() == null )
601                {
602                    if ( parentDistMgmt.getSite() != null )
603                    {
604                        Site site = new Site();
605    
606                        childDistMgmt.setSite( site );
607    
608                        site.setId( parentDistMgmt.getSite().getId() );
609    
610                        site.setName( parentDistMgmt.getSite().getName() );
611    
612                        site.setUrl( parentDistMgmt.getSite().getUrl() );
613    
614                        if ( site.getUrl() != null )
615                        {
616                            site.setUrl(
617                                appendPath( site.getUrl(), child.getArtifactId(), childPathAdjustment, appendPaths ) );
618                        }
619                    }
620                }
621    
622                if ( childDistMgmt.getRepository() == null )
623                {
624                    if ( parentDistMgmt.getRepository() != null )
625                    {
626                        DeploymentRepository repository = copyDistributionRepository( parentDistMgmt.getRepository() );
627                        childDistMgmt.setRepository( repository );
628                    }
629                }
630    
631                if ( childDistMgmt.getSnapshotRepository() == null )
632                {
633                    if ( parentDistMgmt.getSnapshotRepository() != null )
634                    {
635                        DeploymentRepository repository =
636                            copyDistributionRepository( parentDistMgmt.getSnapshotRepository() );
637                        childDistMgmt.setSnapshotRepository( repository );
638                    }
639                }
640    
641                if ( StringUtils.isEmpty( childDistMgmt.getDownloadUrl() ) )
642                {
643                    childDistMgmt.setDownloadUrl( parentDistMgmt.getDownloadUrl() );
644                }
645    
646                // NOTE: We SHOULD NOT be inheriting status, since this is an assessment of the POM quality.
647                // NOTE: We SHOULD NOT be inheriting relocation, since this relates to a single POM
648            }
649        }
650    
651        private static DeploymentRepository copyDistributionRepository( DeploymentRepository parentRepository )
652        {
653            DeploymentRepository repository = new DeploymentRepository();
654    
655            repository.setId( parentRepository.getId() );
656    
657            repository.setName( parentRepository.getName() );
658    
659            repository.setUrl( parentRepository.getUrl() );
660    
661            repository.setLayout( parentRepository.getLayout() );
662    
663            repository.setUniqueVersion( parentRepository.isUniqueVersion() );
664    
665            return repository;
666        }
667    
668        // TODO: This should eventually be migrated to DefaultPathTranslator.
669        protected String appendPath( String parentPath, String childPath, String pathAdjustment, boolean appendPaths )
670        {
671            String uncleanPath = parentPath;
672    
673            if ( appendPaths )
674            {
675                if ( pathAdjustment != null )
676                {
677                    uncleanPath += "/" + pathAdjustment;
678                }
679    
680                if ( childPath != null )
681                {
682                    uncleanPath += "/" + childPath;
683                }
684            }
685    
686            String cleanedPath = "";
687    
688            int protocolIdx = uncleanPath.indexOf( "://" );
689    
690            if ( protocolIdx > -1 )
691            {
692                cleanedPath = uncleanPath.substring( 0, protocolIdx + 3 );
693                uncleanPath = uncleanPath.substring( protocolIdx + 3 );
694            }
695    
696            if ( uncleanPath.startsWith( "/" ) )
697            {
698                cleanedPath += "/";
699            }
700    
701            return cleanedPath + resolvePath( uncleanPath );
702        }
703    
704        // TODO: Move this to plexus-utils' PathTool.
705        private static String resolvePath( String uncleanPath )
706        {
707            LinkedList<String> pathElements = new LinkedList<String>();
708    
709            StringTokenizer tokenizer = new StringTokenizer( uncleanPath, "/" );
710    
711            while ( tokenizer.hasMoreTokens() )
712            {
713                String token = tokenizer.nextToken();
714    
715                if ( token.equals( "" ) )
716                {
717                    // Empty path entry ("...//.."), remove.
718                }
719                else if ( token.equals( ".." ) )
720                {
721                    if ( pathElements.isEmpty() )
722                    {
723                        // FIXME: somehow report to the user
724                        // that there are too many '..' elements.
725                        // For now, ignore the extra '..'.
726                    }
727                    else
728                    {
729                        pathElements.removeLast();
730                    }
731                }
732                else
733                {
734                    pathElements.addLast( token );
735                }
736            }
737    
738            StringBuilder cleanedPath = new StringBuilder();
739    
740            while ( !pathElements.isEmpty() )
741            {
742                cleanedPath.append( pathElements.removeFirst() );
743                if ( !pathElements.isEmpty() )
744                {
745                    cleanedPath.append( '/' );
746                }
747            }
748    
749            return cleanedPath.toString();
750        }
751    
752        private static void mergeExtensionLists( Build childBuild, Build parentBuild )
753        {
754            for ( Extension e : parentBuild.getExtensions() )
755            {
756                if ( !childBuild.getExtensions().contains( e ) )
757                {
758                    childBuild.addExtension( e );
759                }
760            }
761        }
762    }