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).
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 }