Java tutorial
/* * Copyright 2013-2015 smartics, Kronseder & Reiner GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.smartics.maven.plugin.jboss.modules; import java.io.File; import java.io.IOException; import java.util.*; import java.util.Map.Entry; import org.apache.commons.io.FileUtils; import org.apache.maven.archiver.MavenArchiveConfiguration; import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.DependencyManagement; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.jar.JarArchiver; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.collection.DependencySelector; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.DependencyResolutionException; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.util.graph.selector.AndDependencySelector; import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector; import org.eclipse.aether.util.graph.selector.OptionalDependencySelector; import org.eclipse.aether.util.graph.selector.ScopeDependencySelector; import de.smartics.maven.plugin.jboss.modules.aether.Mapper; import de.smartics.maven.plugin.jboss.modules.aether.MavenRepository; import de.smartics.maven.plugin.jboss.modules.aether.MojoRepositoryBuilder; import de.smartics.maven.plugin.jboss.modules.aether.filter.DefaultTransitiveDependencyResolver; import de.smartics.maven.plugin.jboss.modules.aether.filter.ExclusionFilter; import de.smartics.maven.plugin.jboss.modules.aether.filter.GaExclusionFilter; import de.smartics.maven.plugin.jboss.modules.aether.filter.TestScopeFilter; import de.smartics.maven.plugin.jboss.modules.descriptor.ArtifactClusion; import de.smartics.maven.plugin.jboss.modules.descriptor.ModuleDescriptor; import de.smartics.maven.plugin.jboss.modules.descriptor.ModulesDescriptor; import de.smartics.maven.plugin.jboss.modules.domain.ExecutionContext; import de.smartics.maven.plugin.jboss.modules.domain.ModuleBuilder; import de.smartics.maven.plugin.jboss.modules.domain.ModuleMap; import de.smartics.maven.plugin.jboss.modules.domain.PrunerGenerator; import de.smartics.maven.plugin.jboss.modules.domain.SlotStrategy; import de.smartics.maven.plugin.jboss.modules.domain.TransitiveDependencyResolver; import de.smartics.maven.plugin.jboss.modules.parser.ModulesXmlLocator; /** * Generates a archive containing modules from a BOM project. * * @since 1.0 * @description Generates a archive containing modules from a BOM project. */ @Mojo(name = "create-modules-archive", threadSafe = true, requiresProject = true, requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.PACKAGE) public final class JBossModulesArchiveMojo extends AbstractMojo { // ********************************* Fields ********************************* // --- constants ------------------------------------------------------------ // --- members -------------------------------------------------------------- // ... Mojo infrastructure .................................................. /** * The Maven project. * * @since 1.0 */ @Component private MavenProject project; /** * The Maven session. */ @Component private MavenSession session; /** * Resolver for artifact repositories. * * @since 1.0 */ @Component private RepositorySystem repositorySystem; /** * The current repository/network configuration of Maven. */ @Parameter(defaultValue = "${repositorySystemSession}") private RepositorySystemSession repositorySession; /** * The project's remote repositories to use for the resolution of * dependencies. */ @Parameter(defaultValue = "${project.remoteProjectRepositories}") private List<RemoteRepository> remoteRepos; /** * Helper to add attachments to the build. * * @since 1.0 */ @Component private MavenProjectHelper projectHelper; /** * Helper to create an archive. * * @since 1.0 */ @Component(role = Archiver.class, hint = "jar") private JarArchiver jarArchiver; /** * The archive configuration to use. See <a * href="http://maven.apache.org/shared/maven-archiver/index.html">Maven * Archiver Reference</a>. * * @since 1.0 */ @Parameter private final MavenArchiveConfiguration archive = new MavenArchiveConfiguration(); /** * A simple flag to skip the execution of this MOJO. If set on the command * line use <code>-Dsmartics-jboss-modules.skip</code>. * * @since 1.0 */ @Parameter(property = "smartics-jboss-modules.skip", defaultValue = "false") private boolean skip; /** * The verbose level. If set on the command line use * <code>-Dsmartics-jboss-modules.verbose</code>. * * @since 1.0 */ @Parameter(property = "smartics-jboss-modules.verbose", defaultValue = "false") private boolean verbose; /** * Allows to attach the generated modules as a ZIP archive to the build. * * @since 1.0 */ @Parameter(defaultValue = "true") private boolean attach; /** * Controls the system to act as being offline (<code>true</code>) or not ( * <code>false</code>). * * @since 1.0 */ @Parameter(defaultValue = "${offline}") private boolean offline; /** * Controls the update policy according the access of the remote repositories. * <p> * Allowed values are <code>never</code>, <code>always</code>, and * <code>daily</code>. * </p> * * @since 1.0 */ @Parameter(property = "smartics-jboss-modules.update", defaultValue = "never") private String updatePolicy; /** * Controls whether or not optional dependencies should be followed. * * @since 1.0 */ @Parameter(property = "smartics-jboss-modules.followOptionalDependencies", defaultValue = "false") private boolean followOptionalDependencies; /** * Allows to globally ignore exclusions declared in Maven dependencies. * * @since 1.0 */ @Parameter(defaultValue = "false") private boolean ignoreDependencyExclusions; /** * The name of the default slot to write to. See <code>slotStrategy</code>. * * @since 1.0 * @see #slotStrategy */ @Parameter(defaultValue = "main") private String defaultSlot; /** * The name of the slot strategy to us. If not specified, the major version of * the dependency will be used as slot value. * <p> * Possible values are: * </p> * <table> * <tr> * <th>value</th> * <th>description</th> * </tr> * <tr> * <td>version-major</td> * <td>The slot has the major number of the version. The * <code>defaultSlot</code> is prepended, if not set to <code>main</code> * (e.g. <code>defaultSlot</code>=prodx and version 1.2.3 then the slot will * be named prodx1.</td> * </tr> * <tr> * <td>version-full</td> * <td>The slot has the full number of the version. (ex. 1.2.0) </td> * </tr> * <tr> * <td>main</td> * <td>The slot has the name as given with <code>defaultSlot</code>.</td> * </tr> * </table> * * @since 1.0 */ @Parameter(defaultValue = "main") private String slotStrategy; /** * A list of dependencies to be excluded from the transitive dependency * collection process. * * <pre> * <dependencyExcludes> * <exclude> * <groupId>com.sun</groupId> * <artifactId>tools</artifactId> * </exclude> * </dependencyExcludes> * </pre> */ @Parameter private List<ArtifactClusion> dependencyExcludes; /** * The root directories to search for modules XML files that contain module * descriptors. * <p> * If not specified, the default locations * <code>src/main/config/jboss-modules</code>, * <code>src/main/resources/META-INF/jboss-modules</code>, and * <code>src/etc/jboss-modules</code> is probed and - if exists - are * appended. * </p> * <p> * You may want to use only one of the locations given above. Use * <code>config</code> if you do not want to have the configuration files * included. Use <code>resources/META-INF</code> if they should and use * <code>etc</code> if they should not, but be stored outside the * <code>main</code> folder. * </p> * * <pre> * <modules> * <dir>src/etc/jboss-modules</dir> * </modules> * </pre> * * @since 1.0 */ @Parameter private List<String> modules; /** * The folder to write the module structure to. * * @since 1.0 */ @Parameter(defaultValue = "${project.build.directory}/jboss-modules") private File targetFolder; /** * The file to attach, containing the JBoss modules. * * @since 1.0 */ @Parameter(defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}-jboss-modules.jar") private File modulesArchive; /** * The modules declared in the POM and the modules declared on the classpath * (in that order). */ private List<ModulesDescriptor> modulesDescriptors; /** * The linear list of all modules from {@link #modulesDescriptors}. */ private List<ModuleDescriptor> allModules; /** * Exclude the dependencies in the dependency management block if the project * is a POM project. If the project is not a POM project, these dependencies * are never included. * <p> * For BOM projects the default of <code>false</code> is usually appropriate. * In case of a multi module POM, the property usually is set to * <code>true</code>. * </p> * * @since 1.0 */ @Parameter(defaultValue = "false") private boolean excludeDependencyManagementDependenciesInPomProject; /** * Exclude any dependencies or transitive dependencies that are marked as * optional = true. */ @Parameter(defaultValue = "false") private boolean ignoreOptionalDependencies; // ****************************** Initializer ******************************* // ****************************** Constructors ****************************** // ****************************** Inner Classes ***************************** // ********************************* Methods ******************************** // --- init ----------------------------------------------------------------- // --- get&set -------------------------------------------------------------- // --- business ------------------------------------------------------------- @Override public void execute() throws MojoExecutionException, MojoFailureException { final Log log = getLog(); if (skip) { log.info("Skipping creating archive for JBoss modules since skip='true'."); return; } this.modulesDescriptors = initModulesDescriptors(); this.allModules = initModules(); this.repositorySession = adjustSession(); final List<Dependency> rootDependencies = calcRootDependencies(); final List<Dependency> dependencies = resolve(rootDependencies); logDependencies(rootDependencies, dependencies); runModuleCreation(dependencies); attach(); } private List<ModuleDescriptor> initModules() { final List<ModuleDescriptor> modules = new ArrayList<ModuleDescriptor>(); for (final ModulesDescriptor descriptor : modulesDescriptors) { modules.addAll(descriptor.getDescriptors()); } // Make sure the module order stays consistent across all platforms Collections.sort(modules, new Comparator<ModuleDescriptor>() { @Override public int compare(final ModuleDescriptor o1, final ModuleDescriptor o2) { return o1.getName().compareTo(o2.getName()); } }); return modules; } private List<ModulesDescriptor> initModulesDescriptors() throws MojoExecutionException { try { final ModulesXmlLocator locator = new ModulesXmlLocator(defaultSlot); final ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader(); final List<File> rootDirectories = calcModulesRootDirectories(); final List<ModulesDescriptor> descriptors = locator.discover(parentClassLoader, rootDirectories); return descriptors; } catch (final IOException e) { throw new MojoExecutionException("Cannot read modules from class path.", e); } } private List<File> calcModulesRootDirectories() { if (modules == null) { addDefaultDirIfExists("src/etc/jboss-modules"); addDefaultDirIfExists("src/main/config/jboss-modules"); addDefaultDirIfExists("src/main/resources/META-INF/jboss-modules"); } if (modules != null) { final List<File> rootDirectories = new ArrayList<File>(modules.size()); for (final String dir : modules) { final File rootDirectory = new File(project.getBasedir(), dir); if (rootDirectory.isDirectory()) { rootDirectories.add(rootDirectory); } else { getLog().warn(String.format("Modules directory '%s' does not exist. Skipping ...", rootDirectory.getAbsolutePath())); } } return rootDirectories; } return new ArrayList<File>(0); } private void addDefaultDirIfExists(final String defaultDir) { if (modules == null) { modules = new ArrayList<String>(2); } final File rootDirectory = new File(project.getBasedir(), defaultDir); if (rootDirectory.isDirectory()) { modules.add(defaultDir); } } private void runModuleCreation(final List<Dependency> dependencies) throws MojoExecutionException { final boolean isPomProject = "pom".equals(project.getPackaging()); if (!isPomProject || excludeDependencyManagementDependenciesInPomProject) { final Mapper mapper = new Mapper(); final Artifact projectArtifact = mapper.map(project.getArtifact()); final Dependency projectAsDependency = new Dependency(projectArtifact, "compile"); dependencies.add(0, projectAsDependency); } final ExecutionContext context = createContext(dependencies); for (final Entry<ModuleDescriptor, List<Dependency>> entry : context.getModuleMap().toMap().entrySet()) { final ModuleDescriptor module = entry.getKey(); final Collection<Dependency> moduleDependencies = new HashSet<Dependency>(entry.getValue()); final ModuleBuilder builder = new ModuleBuilder(context, module, moduleDependencies); try { builder.create(); } catch (final IOException e) { throw new MojoExecutionException("Cannot write module '" + entry.getKey().getName() + "'.", e); } } } private void logDependencies(final Collection<Dependency> rootDependencies, final Collection<Dependency> dependencies) throws MojoExecutionException { if (verbose) { try { FileUtils.writeStringToFile(new File(project.getBasedir(), "target/root-dependencies.txt"), rootDependencies.toString()); FileUtils.writeStringToFile(new File(project.getBasedir(), "target/resolved-dependencies.txt"), dependencies.toString()); } catch (final IOException e) { throw new MojoExecutionException("dependencies", e); } } } private DefaultRepositorySystemSession adjustSession() { final DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(repositorySession); final Set<DependencySelector> selectors = new HashSet<DependencySelector>(); selectors.add(new ScopeDependencySelector("test")); if (!followOptionalDependencies) { selectors.add(new OptionalDependencySelector()); } selectors.add(new ExclusionDependencySelector()); final AndDependencySelector selector = new AndDependencySelector(selectors); session.setDependencySelector(selector); session.setUpdatePolicy(updatePolicy); session.setOffline(offline); return session; } private void attach() throws MojoExecutionException { if (!attach) { return; } if (!targetFolder.isDirectory()) { getLog().info("Nothing to attach."); return; } try { jarArchiver.addDirectory(targetFolder); final MavenArchiver archiver = new MavenArchiver(); archiver.setArchiver(jarArchiver); archiver.setOutputFile(modulesArchive); archiver.createArchive(session, project, archive); projectHelper.attachArtifact(project, "jar", "jboss-modules", modulesArchive); } catch (final Exception e) { final String message = String.format("Cannot create archive '%s'.: %s", modulesArchive.getAbsolutePath(), e.getMessage()); throw new MojoExecutionException(message, e); } } private ExecutionContext createContext(final List<Dependency> dependencies) { final ExecutionContext.Builder builder = new ExecutionContext.Builder(); builder.with(getLog()); builder.withTargetFolder(targetFolder); final TransitiveDependencyResolver resolver = createResolver(dependencies); builder.with(resolver); final SlotStrategy slotStrategy = SlotStrategy.fromString(this.slotStrategy); builder.with(slotStrategy); builder.withDefaultSlot(defaultSlot); final ModuleMap moduleMap = new ModuleMap(allModules, dependencies); builder.with(moduleMap); builder.with(ignoreOptionalDependencies); if (verbose) { getLog().info("Modules:\n" + moduleMap.toString()); } return builder.build(); } @SuppressWarnings("unchecked") private List<Dependency> calcRootDependencies() throws MojoExecutionException { final List<Dependency> rootDependencies = new ArrayList<Dependency>(); final List<org.apache.maven.model.Dependency> projectDependencies = project.getDependencies(); addMappedDependencies(rootDependencies, projectDependencies); final boolean isPomProject = "pom".equals(project.getPackaging()); if (isPomProject) { if (!excludeDependencyManagementDependenciesInPomProject) { final DependencyManagement management = project.getDependencyManagement(); if (management != null) { final List<org.apache.maven.model.Dependency> managedDependencies = management .getDependencies(); addMappedDependencies(rootDependencies, managedDependencies); } } } return rootDependencies; } private static void addMappedDependencies(final List<Dependency> rootDependencies, final List<org.apache.maven.model.Dependency> newDependencies) { if (newDependencies != null && !newDependencies.isEmpty()) { final Mapper mapper = new Mapper(); for (final org.apache.maven.model.Dependency mavenDependency : newDependencies) { final Dependency dependency = mapper.map(mavenDependency); rootDependencies.add(dependency); } } } private List<Dependency> resolve(final List<Dependency> rootDependencies) throws MojoExecutionException { final TransitiveDependencyResolver resolver = createResolver(null); try { final List<Dependency> dependencies = resolver.resolve(rootDependencies); return dependencies; } catch (final DependencyResolutionException e) { final String message = "Cannot resolve dependency: " + e.getMessage() + "\nYou may use 'dependencyExcludes' to exclude broken dependencies from examination."; throw new MojoExecutionException(message, e); } } private TransitiveDependencyResolver createResolver(final List<Dependency> managedDependencies) { final PrunerGenerator prunerGenerator = new PrunerGenerator(dependencyExcludes, allModules, ignoreDependencyExclusions); final List<DependencyFilter> dependencyFilters = createDependencyFilters(); final MojoRepositoryBuilder builder = new MojoRepositoryBuilder(); builder.with(repositorySystem).with(repositorySession).with(remoteRepos) .withDependencyFilters(dependencyFilters).withManagedDependencies(managedDependencies) .withOffline(offline).withTraverserGenerator(prunerGenerator).build(); final MavenRepository repository = builder.build(); return new DefaultTransitiveDependencyResolver(repository); } private List<DependencyFilter> createDependencyFilters() { final List<DependencyFilter> dependencyFilters = new ArrayList<DependencyFilter>(); dependencyFilters.add(TestScopeFilter.INSTANCE); if (!ignoreDependencyExclusions) { dependencyFilters.add(ExclusionFilter.INSTANCE); } if (dependencyExcludes != null && !dependencyExcludes.isEmpty()) { final GaExclusionFilter filter = new GaExclusionFilter(dependencyExcludes); dependencyFilters.add(filter); } return dependencyFilters; } // --- object basics -------------------------------------------------------- }