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 }