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 }