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