/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.apache.maven.plugin.surefire.parser; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.artifact.metadata.ArtifactMetadataSource; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactCollector; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.dependency.tree.DependencyNode; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException; import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor; import org.apache.maven.surefire.booter.SurefireBooter; import org.apache.maven.surefire.jboss.config.Versions; /** * TODO investigate being able to inject our fields using plexus * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> * @version $Revision: 1.1 $ */ public class ModulesProcessor { private final Log log; private final MavenProject project; private final ArtifactRepository localRepository; private final ArtifactFactory artifactFactory; private final ArtifactMetadataSource metadataSource; private final ArtifactCollector artifactCollector; private final File testClassesDirectory; private final File classesDirectory; private final File moduleDefinitionFile; private final boolean cleanModulesDirectory; private final File modulesDirectory; private final DependencyTreeBuilder dependencyTreeBuilder; private final Map<String, Artifact> dependencyArtifacts = new HashMap<String, Artifact>(); public ModulesProcessor(Log log, MavenProject project, ArtifactRepository localRepository, ArtifactFactory artifactFactory, ArtifactMetadataSource metadataSource, ArtifactCollector artifactCollector, File testClassesDirectory, File classesDirectory, File moduleDefinitionFile, boolean cleanModulesDirectory, File modulesDirectory, DependencyTreeBuilder dependencyTreeBuilder) { this.log = log; this.project = project; this.localRepository = localRepository; this.artifactFactory = artifactFactory; this.metadataSource = metadataSource; this.artifactCollector = artifactCollector; this.testClassesDirectory = testClassesDirectory; this.classesDirectory = classesDirectory; this.moduleDefinitionFile = moduleDefinitionFile; this.cleanModulesDirectory = cleanModulesDirectory; this.modulesDirectory = modulesDirectory; this.dependencyTreeBuilder = dependencyTreeBuilder; } public void createModulesDirectory() throws MojoExecutionException { if (initializeModulesDirectory()) { return; } initializeModuleDefinitonFile(); initializeDependencyArtifacts(); processModules(); } private boolean initializeModulesDirectory() throws MojoExecutionException { if (modulesDirectory.exists()) { if (!cleanModulesDirectory) { log.info("Reusing modules directory " + modulesDirectory.getAbsolutePath() + ". To recreate it next time run with -Djboss.modules.clean=true"); return true; } log.info("Deleting existing modules directory " + modulesDirectory.getAbsolutePath() + ". It will be recreated. " + "To keep it around for the next test-run, run with -Djboss.modules.clean=false"); delete(modulesDirectory); } else { modulesDirectory.mkdirs(); if (!modulesDirectory.exists()){ throw new MojoExecutionException("Could not create directory " + modulesDirectory.getAbsolutePath()); } } return false; } private void initializeModuleDefinitonFile() throws MojoExecutionException { if (!moduleDefinitionFile.exists()) { throw new MojoExecutionException("Could not find module definition file " + moduleDefinitionFile); } } private void initializeDependencyArtifacts() { DependencyNode node; try { node = dependencyTreeBuilder.buildDependencyTree( project, localRepository, artifactFactory, metadataSource, null, artifactCollector ); } catch (DependencyTreeBuilderException e) { throw new RuntimeException(e); } IndexingDependencyNodeVisitor visitor = new IndexingDependencyNodeVisitor(); node.accept(visitor); //These get included implicitly try { dependencyArtifacts.put("org.jboss.maven.surefire.modular:surefire-booter", new DefaultArtifact("org.jboss.maven.surefire.modular", "surefire-booter", VersionRange.createFromVersionSpec(Versions.PLUGIN_FORK_VERSION), "compile", "jar", null, new DefaultArtifactHandler("jar"))); dependencyArtifacts.put("org.apache.maven.surefire:surefire-api", new DefaultArtifact("org.apache.maven.surefire", "surefire-api", VersionRange.createFromVersionSpec(Versions.PROPER_SUREFIRE_VERSION), "compile", "jar", null, new DefaultArtifactHandler("jar"))); } catch (InvalidVersionSpecificationException e) { // AutoGenerated throw new RuntimeException(e); } } private void processModules() { ModulesParser parser = new ModulesParser(moduleDefinitionFile); List<Module> modules = parser.parse(); for (Module module : createSurefireModules(parser)) { processModule(module); } for (Module module : modules) { processModule(module); } } private void processModule(Module module) { File moduleDir = createModuleDirectory(module.getName()); List<MavenReplacement> replacements = new ArrayList<MavenReplacement>(); module.findReplacementAttributes(replacements); for (MavenReplacement replacement : replacements) { File moduleFile = copyMavenArtifact(moduleDir, replacement); if (moduleFile != null) { replacement.updateAttributeValue(moduleFile.getName()); } } module.output(moduleDir); } private File createModuleDirectory(String name) { String path = name.replace('.', File.separatorChar) + File.separator + "main"; File file = new File(modulesDirectory, path); file.mkdirs(); if (!file.exists()) { throw new RuntimeException("Could not create directory " + file.getAbsolutePath()); } return file; } private List<Module> createSurefireModules(ModulesParser parser) { if (!"urn:jboss:module:1.0".equals(parser.getJBossModulesNamespaceUri())) { throw new IllegalArgumentException("Invalid jboss modules version"); } Module module = new Module(parser.getJBossModulesNamespaceUri()); module.addAttribute("name", "jboss.surefire.module"); ChildElement mainClass = new ChildElement("main-class"); mainClass.addAttribute("name", SurefireBooter.class.getName()); module.addChild(mainClass); ChildElement resources = new ChildElement("resources"); addResource(resources, "$org.apache.maven.surefire:surefire-api$"); addResource(resources, "$org.jboss.maven.surefire.modular:surefire-booter$"); module.addChild(resources); TestModuleResources testModuleResources = parser.getTestModuleResources(); if (testModuleResources != null) { if (testModuleResources.getChildren() != null) { for (ChildElement resource : testModuleResources.getChildren()) { resources.addChild(resource); } } } TestModuleDependencies testModuleDependencies = parser.getTestModuleDependencies(); if (testModuleDependencies != null) { if (testModuleDependencies.getChildren() != null) { ChildElement deps = new ChildElement("dependencies"); for (ChildElement dep : testModuleDependencies.getChildren()) { deps.addChild(dep); } module.addChild(deps); } } return Collections.singletonList(module); } private File copyMavenArtifact(File moduleDir, MavenReplacement replacement) { String mavenName = replacement.getAttributeValue(); log.debug("Searching for artifact " + mavenName); int index = mavenName.indexOf(":"); if (index == -1) { if (mavenName.equals("$CLASSES$")) { return copyDirectory(classesDirectory, moduleDir); } else if (mavenName.equals("$TEST.CLASSES$")) { return copyDirectory(testClassesDirectory, moduleDir); } return null; } else { Artifact artifact = dependencyArtifacts.get(mavenName); if (artifact == null) { log.warn("No artifact matching " + mavenName + " found in project dependencies"); return null; } File srcFile = new File(new File(localRepository.getBasedir()), localRepository.pathOf(artifact)); if (srcFile != null) { return copyFileToDir(srcFile, moduleDir); } } return null; } private File copyFileToDir(File src, File destDir) { if (!src.exists()) { throw new IllegalArgumentException("Maven repository jar " + src.getAbsolutePath() + " does not exist"); } if (!destDir.exists() && !destDir.isDirectory()) { throw new IllegalArgumentException("No directory called " + destDir); } String name = src.getName(); File dest = new File(destDir, name); copyFile(src, dest); return dest; } private void copyFile(File src, File dest) { OutputStream out; try { out = new BufferedOutputStream(new FileOutputStream(dest)); } catch (FileNotFoundException e) { throw new RuntimeException(e); } InputStream in; try { in = new BufferedInputStream(new FileInputStream(src)); } catch (FileNotFoundException e) { throw new RuntimeException(e); } try { int i = in.read(); while (i != -1) { out.write(i); i = in.read(); } } catch (IOException e) { throw new RuntimeException(e); } finally { safeClose(in); safeClose(out); } } private File copyDirectory(File src, File destDir) { if (!src.exists()) { throw new IllegalArgumentException(src.getAbsolutePath() + " does not exist"); } if (!destDir.exists() && !destDir.isDirectory()) { throw new IllegalArgumentException("No directory called " + destDir); } String name = src.getName(); File dest = new File(destDir, name); dest.mkdir(); copyDirectoryContents(src, dest); return dest; } private void copyDirectoryContents(File src, File destDir) { for (String curr : src.list()) { File original = new File(src, curr); File copy = new File(destDir, curr); if (original.isDirectory()) { copy.mkdir(); copyDirectoryContents(original, copy); } else { copyFile(original, copy); } } } private static void delete(File file) { if (!file.isDirectory()) { file.delete(); return; } for (String name :file.list()) { delete(new File(file, name)); } file.delete(); } private void safeClose(Closeable c) { try { c.close(); } catch (IOException ignore) { } } public class IndexingDependencyNodeVisitor implements DependencyNodeVisitor { @Override public boolean endVisit(DependencyNode node) { return true; } @Override public boolean visit(DependencyNode node) { if (node.getState() == DependencyNode.INCLUDED) { addArtifact(node.getArtifact()); } return true; } private void addArtifact(Artifact artifact) { String name = artifact.getGroupId() + ":" + artifact.getArtifactId(); Artifact existing = dependencyArtifacts.get(name); if (existing != null && !existing.equals(artifact)) { log.warn("Ignoring found dependency for '" + name + "' since it was already resolved to '" + dependencyArtifacts.get(name) + "'. Ignored value is '" + artifact + "'. Run 'mvn dependency:tree' and clean up your dependencies to avoid duplicates"); } else { dependencyArtifacts.put(name, artifact); } } } private void addResource(ChildElement resources, String placeholder) { ChildElement resource = new ChildElement("resource-root"); resource.addAttribute("path", placeholder); resources.addChild(resource); } }