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