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}