View Javadoc
1   package org.apache.maven.model.inheritance;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.maven.model.Model;
30  import org.apache.maven.model.Plugin;
31  import org.apache.maven.model.PluginContainer;
32  import org.apache.maven.model.ReportPlugin;
33  import org.apache.maven.model.Reporting;
34  import org.apache.maven.model.building.ModelBuildingRequest;
35  import org.apache.maven.model.building.ModelProblemCollector;
36  import org.apache.maven.model.merge.MavenModelMerger;
37  import org.codehaus.plexus.component.annotations.Component;
38  
39  /**
40   * Handles inheritance of model values.
41   *
42   * @author Benjamin Bentmann
43   */
44  @Component( role = InheritanceAssembler.class )
45  public class DefaultInheritanceAssembler
46      implements InheritanceAssembler
47  {
48  
49      private InheritanceModelMerger merger = new InheritanceModelMerger();
50  
51      @Override
52      public void assembleModelInheritance( Model child, Model parent, ModelBuildingRequest request,
53                                            ModelProblemCollector problems )
54      {
55          Map<Object, Object> hints = new HashMap<Object, Object>();
56          hints.put( MavenModelMerger.CHILD_PATH_ADJUSTMENT, getChildPathAdjustment( child, parent ) );
57          merger.merge( child, parent, false, hints );
58      }
59  
60      /**
61       * Calculates the relative path from the base directory of the parent to the parent directory of the base directory
62       * of the child. The general idea is to adjust inherited URLs to match the project layout (in SCM).
63       *
64       * <p>This calculation is only a heuristic based on our conventions.
65       * In detail, the algo relies on the following assumptions: <ul>
66       * <li>The parent uses aggregation and refers to the child via the modules section</li>
67       * <li>The module path to the child is considered to
68       * point at the POM rather than its base directory if the path ends with ".xml" (ignoring case)</li>
69       * <li>The name of the child's base directory matches the artifact id of the child.</li>
70       * </ul>
71       * Note that for the sake of independence from the user
72       * environment, the filesystem is intentionally not used for the calculation.</p>
73       *
74       * @param child The child model, must not be <code>null</code>.
75       * @param parent The parent model, may be <code>null</code>.
76       * @return The path adjustment, can be empty but never <code>null</code>.
77       */
78      private String getChildPathAdjustment( Model child, Model parent )
79      {
80          String adjustment = "";
81  
82          if ( parent != null )
83          {
84              String childName = child.getArtifactId();
85  
86              /*
87               * This logic exists only for the sake of backward-compat with 2.x (MNG-5000). In generally, it is wrong to
88               * base URL inheritance on the project directory names as this information is unavailable for POMs in the
89               * repository. In other words, projects where artifactId != projectDirName will see different effective URLs
90               * depending on how the POM was constructed.
91               */
92              File childDirectory = child.getProjectDirectory();
93              if ( childDirectory != null )
94              {
95                  childName = childDirectory.getName();
96              }
97  
98              for ( String module : parent.getModules() )
99              {
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 }