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}