001    package org.apache.maven.model.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.io.File;
023    import java.util.ArrayList;
024    import java.util.HashMap;
025    import java.util.LinkedHashMap;
026    import java.util.List;
027    import java.util.Map;
028    
029    import org.apache.maven.model.Model;
030    import org.apache.maven.model.Plugin;
031    import org.apache.maven.model.PluginContainer;
032    import org.apache.maven.model.ReportPlugin;
033    import org.apache.maven.model.Reporting;
034    import org.apache.maven.model.building.ModelBuildingRequest;
035    import org.apache.maven.model.building.ModelProblemCollector;
036    import org.apache.maven.model.merge.MavenModelMerger;
037    import org.codehaus.plexus.component.annotations.Component;
038    
039    /**
040     * Handles inheritance of model values.
041     *
042     * @author Benjamin Bentmann
043     */
044    @Component( role = InheritanceAssembler.class )
045    public class DefaultInheritanceAssembler
046        implements InheritanceAssembler
047    {
048    
049        private InheritanceModelMerger merger = new InheritanceModelMerger();
050    
051        public void assembleModelInheritance( Model child, Model parent, ModelBuildingRequest request,
052                                              ModelProblemCollector problems )
053        {
054            Map<Object, Object> hints = new HashMap<Object, Object>();
055            hints.put( MavenModelMerger.CHILD_PATH_ADJUSTMENT, getChildPathAdjustment( child, parent ) );
056            merger.merge( child, parent, false, hints );
057        }
058    
059        /**
060         * Calculates the relative path from the base directory of the parent to the parent directory of the base directory
061         * of the child. The general idea is to adjust inherited URLs to match the project layout (in SCM). This calculation
062         * is only a heuristic based on our conventions. In detail, the algo relies on the following assumptions. The parent
063         * uses aggregation and refers to the child via the modules section. The module path to the child is considered to
064         * point at the POM rather than its base directory if the path ends with ".xml" (ignoring case). The name of the
065         * child's base directory matches the artifact id of the child. Note that for the sake of independence from the user
066         * environment, the filesystem is intentionally not used for the calculation.
067         *
068         * @param child The child model, must not be <code>null</code>.
069         * @param parent The parent model, may be <code>null</code>.
070         * @return The path adjustment, can be empty but never <code>null</code>.
071         */
072        private String getChildPathAdjustment( Model child, Model parent )
073        {
074            String adjustment = "";
075    
076            if ( parent != null )
077            {
078                String childName = child.getArtifactId();
079    
080                /*
081                 * This logic exists only for the sake of backward-compat with 2.x (MNG-5000). In generally, it is wrong to
082                 * base URL inheritance on the project directory names as this information is unavailable for POMs in the
083                 * repository. In other words, projects where artifactId != projectDirName will see different effective URLs
084                 * depending on how the POM was constructed.
085                 */
086                File childDirectory = child.getProjectDirectory();
087                if ( childDirectory != null )
088                {
089                    childName = childDirectory.getName();
090                }
091    
092                for ( String module : parent.getModules() )
093                {
094                    module = module.replace( '\\', '/' );
095    
096                    if ( module.regionMatches( true, module.length() - 4, ".xml", 0, 4 ) )
097                    {
098                        module = module.substring( 0, module.lastIndexOf( '/' ) + 1 );
099                    }
100    
101                    String moduleName = module;
102                    if ( moduleName.endsWith( "/" ) )
103                    {
104                        moduleName = moduleName.substring( 0, moduleName.length() - 1 );
105                    }
106    
107                    int lastSlash = moduleName.lastIndexOf( '/' );
108    
109                    moduleName = moduleName.substring( lastSlash + 1 );
110    
111                    if ( moduleName.equals( childName ) && lastSlash >= 0 )
112                    {
113                        adjustment = module.substring( 0, lastSlash );
114                        break;
115                    }
116                }
117            }
118    
119            return adjustment;
120        }
121    
122        private static class InheritanceModelMerger
123            extends MavenModelMerger
124        {
125    
126            @Override
127            protected void mergePluginContainer_Plugins( PluginContainer target, PluginContainer source,
128                                                         boolean sourceDominant, Map<Object, Object> context )
129            {
130                List<Plugin> src = source.getPlugins();
131                if ( !src.isEmpty() )
132                {
133                    List<Plugin> tgt = target.getPlugins();
134                    Map<Object, Plugin> master = new LinkedHashMap<Object, Plugin>( src.size() * 2 );
135    
136                    for ( Plugin element : src )
137                    {
138                        if ( element.isInherited() || !element.getExecutions().isEmpty() )
139                        {
140                            // NOTE: Enforce recursive merge to trigger merging/inheritance logic for executions
141                            Plugin plugin = new Plugin();
142                            plugin.setLocation( "", element.getLocation( "" ) );
143                            plugin.setGroupId( null );
144                            mergePlugin( plugin, element, sourceDominant, context );
145    
146                            Object key = getPluginKey( element );
147    
148                            master.put( key, plugin );
149                        }
150                    }
151    
152                    Map<Object, List<Plugin>> predecessors = new LinkedHashMap<Object, List<Plugin>>();
153                    List<Plugin> pending = new ArrayList<Plugin>();
154                    for ( Plugin element : tgt )
155                    {
156                        Object key = getPluginKey( element );
157                        Plugin existing = master.get( key );
158                        if ( existing != null )
159                        {
160                            mergePlugin( element, existing, sourceDominant, context );
161    
162                            master.put( key, element );
163    
164                            if ( !pending.isEmpty() )
165                            {
166                                predecessors.put( key, pending );
167                                pending = new ArrayList<Plugin>();
168                            }
169                        }
170                        else
171                        {
172                            pending.add( element );
173                        }
174                    }
175    
176                    List<Plugin> result = new ArrayList<Plugin>( src.size() + tgt.size() );
177                    for ( Map.Entry<Object, Plugin> entry : master.entrySet() )
178                    {
179                        List<Plugin> pre = predecessors.get( entry.getKey() );
180                        if ( pre != null )
181                        {
182                            result.addAll( pre );
183                        }
184                        result.add( entry.getValue() );
185                    }
186                    result.addAll( pending );
187    
188                    target.setPlugins( result );
189                }
190            }
191    
192            @Override
193            protected void mergePlugin( Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context )
194            {
195                if ( source.isInherited() )
196                {
197                    mergeConfigurationContainer( target, source, sourceDominant, context );
198                }
199                mergePlugin_GroupId( target, source, sourceDominant, context );
200                mergePlugin_ArtifactId( target, source, sourceDominant, context );
201                mergePlugin_Version( target, source, sourceDominant, context );
202                mergePlugin_Extensions( target, source, sourceDominant, context );
203                mergePlugin_Dependencies( target, source, sourceDominant, context );
204                mergePlugin_Executions( target, source, sourceDominant, context );
205            }
206    
207            @Override
208            protected void mergeReporting_Plugins( Reporting target, Reporting source, boolean sourceDominant,
209                                                   Map<Object, Object> context )
210            {
211                List<ReportPlugin> src = source.getPlugins();
212                if ( !src.isEmpty() )
213                {
214                    List<ReportPlugin> tgt = target.getPlugins();
215                    Map<Object, ReportPlugin> merged =
216                        new LinkedHashMap<Object, ReportPlugin>( ( src.size() + tgt.size() ) * 2 );
217    
218                    for ( ReportPlugin element :  src )
219                    {
220                        Object key = getReportPluginKey( element );
221                        if ( element.isInherited() )
222                        {
223                            // NOTE: Enforce recursive merge to trigger merging/inheritance logic for executions as well
224                            ReportPlugin plugin = new ReportPlugin();
225                            plugin.setLocation( "", element.getLocation( "" ) );
226                            plugin.setGroupId( null );
227                            mergeReportPlugin( plugin, element, sourceDominant, context );
228    
229                            merged.put( key, plugin );
230                        }
231                    }
232    
233                    for ( ReportPlugin element : tgt )
234                    {
235                        Object key = getReportPluginKey( element );
236                        ReportPlugin existing = merged.get( key );
237                        if ( existing != null )
238                        {
239                            mergeReportPlugin( element, existing, sourceDominant, context );
240                        }
241                        merged.put( key, element );
242                    }
243    
244                    target.setPlugins( new ArrayList<ReportPlugin>( merged.values() ) );
245                }
246            }
247        }
248    
249    }