001package org.andromda.maven.plugin.andromdapp; 002 003import java.io.File; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collection; 007import java.util.LinkedHashMap; 008import java.util.LinkedHashSet; 009import java.util.List; 010import java.util.Map; 011import java.util.Set; 012import org.andromda.core.common.ResourceUtils; 013import org.andromda.maven.plugin.andromdapp.eclipse.ClasspathWriter; 014import org.andromda.maven.plugin.andromdapp.eclipse.ProjectWriter; 015import org.andromda.maven.plugin.andromdapp.eclipse.Variable; 016import org.andromda.maven.plugin.andromdapp.utils.ProjectUtils; 017import org.apache.commons.lang.ObjectUtils; 018import org.apache.commons.lang.StringUtils; 019import org.apache.maven.artifact.factory.ArtifactFactory; 020import org.apache.maven.artifact.metadata.ArtifactMetadataSource; 021import org.apache.maven.artifact.repository.ArtifactRepository; 022import org.apache.maven.artifact.resolver.ArtifactNotFoundException; 023import org.apache.maven.artifact.resolver.ArtifactResolutionException; 024import org.apache.maven.artifact.resolver.ArtifactResolver; 025import org.apache.maven.execution.MavenSession; 026import org.apache.maven.model.Build; 027import org.apache.maven.model.Plugin; 028import org.apache.maven.model.PluginExecution; 029import org.apache.maven.model.PluginManagement; 030import org.apache.maven.plugin.AbstractMojo; 031import org.apache.maven.plugin.MojoExecutionException; 032import org.apache.maven.project.MavenProject; 033import org.apache.maven.project.MavenProjectBuilder; 034import org.apache.maven.project.ProjectBuildingException; 035import org.codehaus.plexus.util.DirectoryScanner; 036import org.codehaus.plexus.util.xml.Xpp3Dom; 037 038/** 039 * Writes the necessary .classpath and .project files 040 * for a new eclipse application. 041 * 042 * @goal eclipse 043 * @phase generate-sources 044 * @author Chad Brandon 045 */ 046public class EclipseMojo 047 extends AbstractMojo 048{ 049 /** 050 * @parameter expression="${session}" 051 */ 052 private MavenSession session; 053 054 /** 055 * @parameter expression="${project}" 056 * @required 057 * @readonly 058 */ 059 private MavenProject project; 060 061 private static final String POM_FILE_NAME = "pom.xml"; 062 063 /** 064 * Defines the POMs to include when generating the eclipse files. 065 * 066 * @parameter 067 */ 068 private String[] includes = new String[] {"*/**/" + POM_FILE_NAME}; 069 070 /** 071 * Defines the POMs to exclude when generating the eclipse files. 072 * 073 * @parameter expression="${exclude.poms}" 074 */ 075 private String excludePoms; 076 077 /** 078 * Defines the POMs to exclude when generating the eclipse files. 079 * 080 * @parameter 081 */ 082 private String[] excludes = new String[0]; 083 084 /** 085 * Used to construct Maven project instances from POMs. 086 * 087 * @component 088 */ 089 private MavenProjectBuilder projectBuilder; 090 091 /** 092 * The name of the variable that will store the maven repository location. 093 * 094 * @parameter expression="${repository.variable.name} 095 */ 096 private String repositoryVariableName = "M2_REPO"; 097 098 /** 099 * Artifact factory, needed to download source jars for inclusion in classpath. 100 * 101 * @component role="org.apache.maven.artifact.factory.ArtifactFactory" 102 * @required 103 * @readonly 104 */ 105 private ArtifactFactory artifactFactory; 106 107 /** 108 * Artifact resolver, needed to download source jars for inclusion in classpath. 109 * 110 * @component role="org.apache.maven.artifact.resolver.ArtifactResolver" 111 * @required 112 * @readonly 113 */ 114 private ArtifactResolver artifactResolver; 115 116 /** 117 * @parameter expression="${localRepository}" 118 * @required 119 * @readonly 120 */ 121 protected ArtifactRepository localRepository; 122 123 /** 124 * @parameter 125 */ 126 protected Variable[] variables; 127 128 /** 129 * @component 130 */ 131 private ArtifactMetadataSource artifactMetadataSource; 132 133 /** 134 * The artifact types which should be included in the generated Eclipse classpath. 135 * 136 * @parameter 137 */ 138 private Set<String> classpathArtifactTypes = new LinkedHashSet<String>(Arrays.asList("jar","ejb")); 139 140 /** 141 * Whether or not transitive dependencies shall be included in any resources (i.e. .classpath 142 * that are generated by this mojo). 143 * 144 * @parameter expression="${resolveTransitiveDependencies}" 145 */ 146 private boolean resolveTransitiveDependencies = true; 147 148 /** 149 * Allows non-generated configuration to be "merged" into the generated .classpath file. 150 * 151 * @parameter 152 */ 153 private String classpathMerge; 154 155 /** 156 * Whether or not processing should be skipped (this is if you just want to force AndroMDA 157 * not to run on your model). 158 * 159 * @parameter expression="${andromda.run.skip}" 160 */ 161 private boolean skipProcessing = false; 162 163 /** 164 * @see org.apache.maven.plugin.Mojo#execute() 165 */ 166 public void execute() 167 throws MojoExecutionException 168 { 169 if (!this.skipProcessing) 170 { 171 try 172 { 173 final MavenProject rootProject = this.getRootProject(); 174 final ProjectWriter projectWriter = new ProjectWriter(rootProject, this.getLog()); 175 projectWriter.write(); 176 final Map<MavenProject, Collection<String>> originalCompileSourceRoots = this.collectProjectCompileSourceRoots(); 177 final List<MavenProject> projects = this.collectProjects(); 178 this.processCompileSourceRoots(projects); 179 final ClasspathWriter classpathWriter = new ClasspathWriter(rootProject, this.getLog()); 180 classpathWriter.write( 181 projects, 182 this.repositoryVariableName, 183 this.artifactFactory, 184 this.artifactResolver, 185 this.localRepository, 186 this.artifactMetadataSource, 187 this.classpathArtifactTypes, 188 this.project.getRemoteArtifactRepositories(), 189 this.resolveTransitiveDependencies, 190 this.variables, 191 this.classpathMerge); 192 // - reset to the original source roots 193 for (final MavenProject project : projects) 194 { 195 project.getCompileSourceRoots().clear(); 196 project.getCompileSourceRoots().addAll(originalCompileSourceRoots.get(project)); 197 } 198 } 199 catch (Throwable throwable) 200 { 201 throw new MojoExecutionException("Error creating eclipse configuration", throwable); 202 } 203 } 204 } 205 206 /** 207 * Collects all existing project compile source roots. 208 * 209 * @return a collection of collections 210 */ 211 private Map<MavenProject, Collection<String>> collectProjectCompileSourceRoots() 212 throws Exception 213 { 214 final Map<MavenProject, Collection<String>> sourceRoots = new LinkedHashMap<MavenProject, Collection<String>>(); 215 for (final MavenProject project : this.collectProjects()) 216 { 217 sourceRoots.put(project, new ArrayList<String>(project.getCompileSourceRoots())); 218 } 219 return sourceRoots; 220 } 221 222 private List<MavenProject> projects = new ArrayList<MavenProject>(); 223 224 /** 225 * Collects all projects from all POMs within the current project. 226 * 227 * @return all applicable Maven project instances. 228 * 229 * @throws MojoExecutionException 230 */ 231 private List<MavenProject> collectProjects() 232 throws Exception 233 { 234 if (projects.isEmpty()) 235 { 236 final List<File> poms = this.getPoms(); 237 for (File pom : poms) 238 { 239 try 240 { 241 // - first attempt to get the existing project from the session 242 final MavenProject project = ProjectUtils.getProject(this.projectBuilder, this.session, pom, this.getLog()); 243 if (project != null) 244 { 245 this.getLog().info("found project " + project.getId()); 246 projects.add(project); 247 } 248 else 249 { 250 if (this.getLog().isWarnEnabled()) 251 { 252 this.getLog().warn("Could not load project from pom: " + pom + " - ignoring"); 253 } 254 } 255 } 256 catch (ProjectBuildingException exception) 257 { 258 throw new MojoExecutionException("Error loading " + pom, exception); 259 } 260 } 261 } 262 return projects; 263 } 264 265 /** 266 * Processes the project compile source roots (adds all appropriate ones to the projects) 267 * so that they're available to the eclipse mojos. 268 * 269 * @param projects the projects to process. 270 * @throws Exception 271 */ 272 private void processCompileSourceRoots(final List<MavenProject> projects) 273 throws Exception 274 { 275 for (final MavenProject project : projects) 276 { 277 final Set<String> compileSourceRoots = new LinkedHashSet<String>(project.getCompileSourceRoots()); 278 compileSourceRoots.addAll(this.getExtraSourceDirectories(project)); 279 final String testSourceDirectory = project.getBuild().getTestSourceDirectory(); 280 if (StringUtils.isNotBlank(testSourceDirectory)) 281 { 282 compileSourceRoots.add(testSourceDirectory); 283 } 284 project.getCompileSourceRoots().clear(); 285 project.getCompileSourceRoots().addAll(compileSourceRoots); 286 } 287 } 288 289 /** 290 * The artifact id for the multi source plugin. 291 */ 292 private static final String MULTI_SOURCE_PLUGIN_ARTIFACT_ID = "build-helper-maven-plugin"; 293 294 /** 295 * Retrieves any additional source directories which are defined within the build-helper-maven-plugin. 296 * 297 * @param project the maven project from which to retrieve the extra source directories. 298 * @return the list of extra source directories. 299 */ 300 private List<String> getExtraSourceDirectories(final MavenProject project) 301 { 302 final List<String> sourceDirectories = new ArrayList<String>(); 303 final Build build = project.getBuild(); 304 if (build != null) 305 { 306 final PluginManagement pluginManagement = build.getPluginManagement(); 307 if (pluginManagement != null && !pluginManagement.getPlugins().isEmpty()) 308 { 309 Plugin multiSourcePlugin = null; 310 for (final Plugin plugin : pluginManagement.getPlugins()) 311 { 312 if (MULTI_SOURCE_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId())) 313 { 314 multiSourcePlugin = plugin; 315 break; 316 } 317 } 318 final Xpp3Dom configuration = this.getConfiguration(multiSourcePlugin); 319 if (configuration != null && configuration.getChildCount() > 0) 320 { 321 final Xpp3Dom directories = configuration.getChild(0); 322 if (directories != null) 323 { 324 final int childCount = directories.getChildCount(); 325 if (childCount > 0) 326 { 327 final String baseDirectory = 328 ResourceUtils.normalizePath(ObjectUtils.toString(project.getBasedir()) + '/'); 329 final Xpp3Dom[] children = directories.getChildren(); 330 for (int ctr = 0; ctr < childCount; ctr++) 331 { 332 final Xpp3Dom child = children[ctr]; 333 if (child != null) 334 { 335 String directoryValue = ResourceUtils.normalizePath(child.getValue()); 336 if (directoryValue != null) 337 { 338 if (!directoryValue.startsWith(baseDirectory)) 339 { 340 directoryValue = 341 ResourceUtils.normalizePath(baseDirectory + directoryValue.trim()); 342 } 343 sourceDirectories.add(directoryValue); 344 } 345 } 346 } 347 } 348 } 349 } 350 } 351 } 352 return sourceDirectories; 353 } 354 355 /** 356 * Retrieves the appropriate configuration instance (first tries 357 * to get the configuration from the plugin, then tries from the plugin's 358 * executions. 359 * 360 * @param plugin the plugin from which the retrieve the configuration. 361 * @return the plugin's configuration, or null if not found. 362 */ 363 private Xpp3Dom getConfiguration(final Plugin plugin) 364 { 365 Xpp3Dom configuration = null; 366 if (plugin != null) 367 { 368 if (plugin.getConfiguration() != null) 369 { 370 configuration = (Xpp3Dom)plugin.getConfiguration(); 371 } 372 else 373 { 374 final List<PluginExecution> executions = plugin.getExecutions(); 375 if (executions != null && !executions.isEmpty()) 376 { 377 // - there should only be one execution so we get the first one 378 final PluginExecution execution = plugin.getExecutions().iterator().next(); 379 configuration = (Xpp3Dom)execution.getConfiguration(); 380 } 381 } 382 } 383 return configuration; 384 } 385 386 /** 387 * Stores the root project. 388 */ 389 private MavenProject rootProject; 390 391 /** 392 * Retrieves the root project (i.e. the root parent project) 393 * for this project. 394 * 395 * @return the root project. 396 * @throws MojoExecutionException 397 */ 398 private MavenProject getRootProject() 399 throws MojoExecutionException, ArtifactResolutionException, ArtifactNotFoundException 400 { 401 if (this.rootProject == null) 402 { 403 final MavenProject firstParent = this.project.getParent(); 404 File rootFile = this.project.getFile(); 405 if (firstParent != null && firstParent.getFile() != null ) 406 { 407 for (this.rootProject = firstParent, rootFile = new File(rootFile.getParentFile().getParentFile(), POM_FILE_NAME); 408 this.rootProject.getParent() != null && this.rootProject.getParent().getFile() != null; 409 this.rootProject = this.rootProject.getParent(), rootFile = new File(rootFile.getParentFile().getParentFile(), POM_FILE_NAME)) 410 { 411 } 412 // - if the project has no file defined, use the rootFile 413 if (this.rootProject != null && this.rootProject.getFile() == null && rootFile.exists()) 414 { 415 this.rootProject.setFile(rootFile); 416 } 417 } 418 else 419 { 420 this.rootProject = this.project; 421 } 422 } 423 return this.rootProject; 424 } 425 426 /** 427 * Retrieves all the POMs for the given project. 428 * 429 * @return all poms found. 430 * @throws MojoExecutionException 431 */ 432 private List<File> getPoms() 433 throws Exception 434 { 435 final DirectoryScanner scanner = new DirectoryScanner(); 436 scanner.setBasedir(this.getRootProject().getBasedir()); 437 scanner.setIncludes(this.includes); 438 439 final List<String> excludes = new ArrayList<String>(Arrays.asList(this.excludes)); 440 if (this.excludePoms != null) 441 { 442 excludes.addAll(Arrays.asList(excludePoms.split(","))); 443 } 444 scanner.setExcludes(excludes.toArray(new String[excludes.size()])); 445 scanner.scan(); 446 447 List<File> poms = new ArrayList<File>(); 448 449 for (int ctr = 0; ctr < scanner.getIncludedFiles().length; ctr++) 450 { 451 final File file = new File( 452 this.getRootProject().getBasedir(), 453 scanner.getIncludedFiles()[ctr]); 454 if (file.exists()) 455 { 456 poms.add(file); 457 } 458 } 459 460 return poms; 461 } 462}