Java tutorial
/* * Copyright 2015 The original authors. * * 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 io.sundr.maven; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import io.sundr.codegen.generator.CodeGeneratorBuilder; import io.sundr.maven.filter.Filters; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; import org.apache.maven.artifact.resolver.ArtifactResolutionResult; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.internal.LifecycleModuleBuilder; import org.apache.maven.lifecycle.internal.LifecycleTaskSegmentCalculator; import org.apache.maven.lifecycle.internal.ProjectIndex; import org.apache.maven.lifecycle.internal.ReactorBuildStatus; import org.apache.maven.lifecycle.internal.ReactorContext; import org.apache.maven.lifecycle.internal.TaskSegment; import org.apache.maven.model.Build; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginManagement; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; 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.project.MavenProject; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.RemoteRepository; @Mojo(name = "generate-bom", inheritByDefault = false, defaultPhase = LifecyclePhase.VALIDATE) public class GenerateBomMojo extends AbstractSundrioMojo { private static final Set<String> GENERATED_ARTIFACT_IDS = Collections.synchronizedSet(new HashSet<String>()); @Component private ArtifactResolver artifactResolver; @Component private LifecycleModuleBuilder builder; @Component private LifecycleTaskSegmentCalculator segmentCalculator; @Component private RepositorySystem aetherSystem; @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true) private RepositorySystemSession aetherSession; @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) private List<RemoteRepository> aetherRemoteRepositories; /** * Location of the localRepository repository. */ @Parameter(defaultValue = "${localRepository}", readonly = true, required = true) private ArtifactRepository localRepository; /** * List of Remote Repositories used by the resolver */ @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true) protected List<ArtifactRepository> remoteRepositories; @Parameter private BomConfig[] boms; @Parameter(defaultValue = "${reactorProjects}", required = true, readonly = false) private List<MavenProject> reactorProjects; @Parameter(defaultValue = "${maven.version}") private String mavenVersion; @Parameter(defaultValue = "${bom.template.resource}") private String bomTemplateResource = "templates/bom.xml.vm"; @Parameter(defaultValue = "${bom.template.url}") private URL bomTemplateUrl; @Override public void execute() throws MojoExecutionException, MojoFailureException { if (getProject().isExecutionRoot() && !getProject().getModules().isEmpty()) { List<MavenProject> updated = new LinkedList<MavenProject>(); Map<BomConfig, MavenProject> generated = new HashMap<BomConfig, MavenProject>(); updated.add(getProject()); if (boms == null || boms.length == 0) { String artifactId = getProject().getArtifactId() + "-bom"; if (GENERATED_ARTIFACT_IDS.add(artifactId)) { BomConfig cfg = new BomConfig(artifactId, getProject().getName() + " Bom", " Generated bom"); MavenProject bomProject = generateBom(cfg); generated.put(cfg, bomProject); updated.add(bomProject); } } else { for (BomConfig cfg : boms) { if (GENERATED_ARTIFACT_IDS.add(cfg.getArtifactId())) { MavenProject bomProject = generateBom(cfg); generated.put(cfg, bomProject); updated.add(bomProject); } } } updated.addAll(getAllButCurrent()); for (Map.Entry<BomConfig, MavenProject> entry : generated.entrySet()) { build(getSession().clone(), entry.getValue(), updated, entry.getKey().getGoals()); } } } private MavenProject generateBom(BomConfig config) throws MojoFailureException, MojoExecutionException { File outputDir = new File(getProject().getBuild().getOutputDirectory()); File bomDir = new File(outputDir, config.getArtifactId()); File generatedBom = new File(bomDir, BOM_NAME); if (!bomDir.exists() && !bomDir.mkdirs()) { throw new MojoFailureException("Failed to create output dir for bom:" + bomDir.getAbsolutePath()); } preProccessConfig(config); FileWriter writer = null; try { writer = new FileWriter(generatedBom); // Imported dependencies may have important additional information (eg. exclusions) // Taking both the artifacts and their related dependencies Map<Artifact, Dependency> dependencies = new LinkedHashMap<Artifact, Dependency>(); Set<Artifact> plugins = new LinkedHashSet<Artifact>(); Set<Artifact> ownModules = Filters.filter(getReactorArtifacts(), Filters.createModulesFilter(config)); //We add first project management and unwrapped boms. (we don't resolve those). Set<Artifact> dependencyManagementArtifacts = config.isInheritDependencyManagement() ? getProjectDependencyManagement() : new LinkedHashSet<Artifact>(); Set<Artifact> pluginManagementArtifacts = config.isInheritPluginManagement() ? getProjectPluginManagement() : new LinkedHashSet<Artifact>(); Set<Artifact> allDependencies = new LinkedHashSet<Artifact>(dependencyManagementArtifacts); allDependencies.addAll(getDependencies(getProjectDependencies())); //We resolve transitives here.... //Populate dependencies dependencies.putAll(toDependencyMap(ownModules)); dependencies.putAll(toDependencyMap( Filters.filter(allDependencies, Filters.createDependencyFilter(getSession(), config)))); //Populate dependencies with imported boms. ExternalBomResolver bomResolver = new ExternalBomResolver(getSession(), getArtifactHandler(), aetherSystem, aetherSession, aetherRemoteRepositories, getLog()); Map<Artifact, Dependency> externalDependencies = bomResolver.resolve(config); dependencies.putAll(externalDependencies); //Populate plugins plugins.addAll(Filters.filter(ownModules, Filters.MAVEN_PLUGIN_FILTER)); plugins.addAll( Filters.filter(pluginManagementArtifacts, Filters.createPluginFilter(getSession(), config))); //Checking version mismatches. MavenProject projectToGenerate = toGenerate(getProject(), config, dependencies.values(), plugins); verifyBomDependencies(config, projectToGenerate); getLog().info("Generating BOM: " + config.getArtifactId()); new CodeGeneratorBuilder<Model>().withWriter(writer).withModel(projectToGenerate.getModel()) .withTemplateResource(bomTemplateResource).withTemplateUrl(bomTemplateUrl).build().generate(); return toBuild(getProject(), config); } catch (Exception e) { throw new MojoFailureException("Failed to generate bom.", e); } finally { try { if (writer != null) { writer.close(); } } catch (IOException e) { throw new MojoExecutionException("Failed to close the generated bom writer", e); } } } private void verifyBomDependencies(BomConfig config, MavenProject project) throws MojoFailureException { if (!config.isCheckMismatches()) { return; } Map<String, Set<String>> versions = new TreeMap<String, Set<String>>(); Set<String> mismatches = new TreeSet<String>(); if (project.getDependencyManagement() != null && project.getDependencyManagement().getDependencies() != null) { for (Dependency dependency : project.getDependencyManagement().getDependencies()) { String key = dependencyKey(dependency); String version = dependency.getVersion(); if (!versions.containsKey(key)) { versions.put(key, new TreeSet<String>()); } for (String otherVersion : versions.get(key)) { if (!version.equals(otherVersion)) { mismatches.add(key); break; } } versions.get(key).add(version); } } if (mismatches.size() > 0) { StringBuilder message = new StringBuilder(); message.append("The BOM " + config.getArtifactId() + " contains multiple versions of the following dependencies:\n"); for (String key : mismatches) { message.append(" - " + key + " versions " + versions.get(key) + "\n"); } if (config.isFailOnMismatch()) { throw new MojoFailureException(message.toString()); } else { getLog().warn(message.toString()); } } } private Set<Artifact> getReactorArtifacts() { Set<Artifact> reactorArtifacts = new LinkedHashSet<Artifact>(); for (MavenProject project : reactorProjects) { reactorArtifacts.add(project.getArtifact()); } return reactorArtifacts; } /** * Returns the model of the {@link org.apache.maven.project.MavenProject} to generate. * This is a trimmed down version and contains just the stuff that need to go into the bom. * * @param project The source {@link org.apache.maven.project.MavenProject}. * @param config The {@link io.sundr.maven.BomConfig}. * @return The build {@link org.apache.maven.project.MavenProject}. */ private static MavenProject toGenerate(MavenProject project, BomConfig config, Collection<Dependency> dependencies, Set<Artifact> plugins) { MavenProject toGenerate = project.clone(); toGenerate.setGroupId(project.getGroupId()); toGenerate.setArtifactId(config.getArtifactId()); toGenerate.setVersion(project.getVersion()); toGenerate.setPackaging("pom"); toGenerate.setName(config.getName()); toGenerate.setDescription(config.getDescription()); toGenerate.setUrl(project.getUrl()); toGenerate.setLicenses(project.getLicenses()); toGenerate.setScm(project.getScm()); toGenerate.setDevelopers(project.getDevelopers()); toGenerate.getModel().setDependencyManagement(new DependencyManagement()); for (Dependency dependency : dependencies) { toGenerate.getDependencyManagement().addDependency(dependency); } toGenerate.getModel().setBuild(new Build()); if (!plugins.isEmpty()) { toGenerate.getModel().setBuild(new Build()); toGenerate.getModel().getBuild().setPluginManagement(new PluginManagement()); for (Artifact artifact : plugins) { toGenerate.getPluginManagement().addPlugin(toPlugin(artifact)); } } return toGenerate; } /** * Returns the generated {@link org.apache.maven.project.MavenProject} to build. * This version of the project contains all the stuff needed for building (parents, profiles, properties etc). * * @param project The source {@link org.apache.maven.project.MavenProject}. * @param config The {@link io.sundr.maven.BomConfig}. * @return The build {@link org.apache.maven.project.MavenProject}. */ private static MavenProject toBuild(MavenProject project, BomConfig config) { File outputDir = new File(project.getBuild().getOutputDirectory()); File bomDir = new File(outputDir, config.getArtifactId()); File generatedBom = new File(bomDir, BOM_NAME); MavenProject toBuild = project.clone(); //we want to avoid recursive "generate-bom". toBuild.setExecutionRoot(false); toBuild.setFile(generatedBom); toBuild.getModel().setPomFile(generatedBom); toBuild.setModelVersion(project.getModelVersion()); toBuild.setArtifact(new DefaultArtifact(project.getGroupId(), config.getArtifactId(), project.getVersion(), project.getArtifact().getScope(), project.getArtifact().getType(), project.getArtifact().getClassifier(), project.getArtifact().getArtifactHandler())); toBuild.setParent(project.getParent()); toBuild.getModel().setParent(project.getModel().getParent()); toBuild.setGroupId(project.getGroupId()); toBuild.setArtifactId(config.getArtifactId()); toBuild.setVersion(project.getVersion()); toBuild.setPackaging("pom"); toBuild.setName(config.getName()); toBuild.setDescription(config.getDescription()); toBuild.setUrl(project.getUrl()); toBuild.setLicenses(project.getLicenses()); toBuild.setScm(project.getScm()); toBuild.setDevelopers(project.getDevelopers()); toBuild.setDistributionManagement(project.getDistributionManagement()); toBuild.getModel().setProfiles(project.getModel().getProfiles()); //We want to avoid having the generated stuff wiped. toBuild.getProperties().put("clean.skip", "true"); toBuild.getModel().getBuild().setDirectory(bomDir.getAbsolutePath()); toBuild.getModel().getBuild().setOutputDirectory(new File(bomDir, "target").getAbsolutePath()); for (String key : config.getProperties().stringPropertyNames()) { toBuild.getProperties().put(key, config.getProperties().getProperty(key)); } return toBuild; } private List<MavenProject> getAllButCurrent() { List<MavenProject> result = new LinkedList<MavenProject>(getSession().getProjects()); result.remove(getSession().getCurrentProject()); return result; } private void build(MavenSession session, MavenProject project, List<MavenProject> allProjects, GoalSet goals) throws MojoExecutionException { session.setProjects(allProjects); ProjectIndex projectIndex = new ProjectIndex(session.getProjects()); try { ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus( new BomDependencyGraph(session.getProjects())); ReactorContext reactorContext = new ReactorContextFactory(new MavenVersion(mavenVersion)).create( session.getResult(), projectIndex, Thread.currentThread().getContextClassLoader(), reactorBuildStatus, builder); List<TaskSegment> segments = segmentCalculator.calculateTaskSegments(session); for (TaskSegment segment : segments) { builder.buildProject(session, reactorContext, project, filterSegment(segment, goals)); } } catch (Throwable t) { throw new MojoExecutionException("Error building generated bom:" + project.getArtifactId(), t); } } /** * Returns all the session/reactor artifacts topologically sorted. * * @return */ private Set<Artifact> getSessionArtifacts() { Set<Artifact> result = new LinkedHashSet<Artifact>(); for (MavenProject p : getSession().getProjectDependencyGraph().getSortedProjects()) { result.add(p.getArtifact()); } return result; } /** * Returns all dependency artifacts in all modules, excluding all reactor artifacts (including attached). * * @return */ private Set<Artifact> getProjectDependencies() { Set<Artifact> result = new LinkedHashSet<Artifact>(); for (MavenProject p : getSession().getProjectDependencyGraph().getSortedProjects()) { for (Dependency dependency : p.getDependencies()) { result.add(toArtifact(dependency)); } } return result; } /** * Returns all dependencies defined in dependency management of the root pom. * * @return */ private Set<Artifact> getProjectDependencyManagement() { Set<Artifact> result = new LinkedHashSet<Artifact>(); DependencyManagement dependencyManagement = getProject().getDependencyManagement(); if (dependencyManagement != null) { for (Dependency dependency : dependencyManagement.getDependencies()) { result.add(toArtifact(dependency)); } } return result; } /** * Returns all dependencies defined in dependency management of the root pom. * * @return */ private Set<Artifact> getProjectPluginManagement() { Set<Artifact> result = new LinkedHashSet<Artifact>(); PluginManagement pluginManagement = getProject().getPluginManagement(); if (pluginManagement != null) { for (Plugin plugin : pluginManagement.getPlugins()) { result.add(toArtifact(plugin)); } } return result; } /** * Collects dependencies, including transitives. * Project dependencies retain their scope, while test only dependencies (including transitives) will have test scope. * @param projectDependencies * @return */ private Set<Artifact> getDependencies(final Set<Artifact> projectDependencies) { Set<Artifact> result = new LinkedHashSet<Artifact>(projectDependencies); Set<Artifact> testDependencies = dependenciesWithScope(projectDependencies, Artifact.SCOPE_TEST); Set<Artifact> nonTestDependencies = allBut(projectDependencies, testDependencies); Set<Artifact> testTransitives = resolve(testDependencies); Set<Artifact> nonTestTransitives = resolve(nonTestDependencies); Set<Artifact> testOnlyDependencies = allBut(testTransitives, nonTestTransitives); for (Artifact testOnly : testOnlyDependencies) { result.add(new DefaultArtifact(testOnly.getGroupId(), testOnly.getArtifactId(), testOnly.getVersion(), Artifact.SCOPE_TEST, testOnly.getType(), testOnly.getClassifier(), testOnly.getArtifactHandler())); } result.addAll(resolve(projectDependencies)); return result; } private Set<Artifact> resolve(final Set<Artifact> dependencies) { ArtifactResolutionRequest request = new ArtifactResolutionRequest(); request.setArtifact(getProject().getArtifact()); request.setArtifactDependencies(dependencies); request.setLocalRepository(localRepository); request.setRemoteRepositories(remoteRepositories); request.setManagedVersionMap(getProject().getManagedVersionMap()); request.setResolveTransitively(true); ArtifactResolutionResult result = artifactResolver.resolve(request); return result.getArtifacts(); } private Artifact toArtifact(Dependency dependency) { return new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), dependency.getScope(), dependency.getType(), dependency.getClassifier(), getArtifactHandler()); } private Artifact toArtifact(Plugin plugin) { return new DefaultArtifact(plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion(), null, Constants.MAVEN_PLUGIN_TYPE, null, getArtifactHandler()); } private static Map<Artifact, Dependency> toDependencyMap(Collection<Artifact> artifacts) { Map<Artifact, Dependency> dependencyMap = new LinkedHashMap<Artifact, Dependency>(); if (artifacts != null) { for (Artifact artifact : artifacts) { Dependency dependency = toDependency(artifact); dependencyMap.put(artifact, dependency); } } return dependencyMap; } private static Dependency toDependency(Artifact artifact) { Dependency dependency = new Dependency(); dependency.setGroupId(artifact.getGroupId()); dependency.setArtifactId(artifact.getArtifactId()); dependency.setVersion(artifact.getVersion()); dependency.setType(artifact.getType()); dependency.setScope(Artifact.SCOPE_COMPILE.equals(artifact.getScope()) ? null : artifact.getScope()); dependency.setClassifier(artifact.getClassifier()); dependency.setOptional(artifact.isOptional()); return dependency; } private static Plugin toPlugin(Artifact artifact) { Plugin plugin = new Plugin(); plugin.setGroupId(artifact.getGroupId()); plugin.setArtifactId(artifact.getArtifactId()); plugin.setVersion(artifact.getVersion()); return plugin; } private static TaskSegment filterSegment(TaskSegment segment, GoalSet goals) { List<Object> filtered = new ArrayList<Object>(); Set<String> includes = goals.getIncludes(); Set<String> excludes = goals.getExcludes(); for (Object obj : segment.getTasks()) { String name = Reflections.readAnyField(obj, "pluginGoal", "lifecyclePhase"); if (!excludes.contains(name) && (includes.contains(name) || includes.isEmpty())) { filtered.add(obj); } } return new TaskSegment(segment.isAggregating(), filtered.toArray()); } private static void preProccessConfig(BomConfig config) { if (config.getModules().getIncludes().isEmpty()) { config.getModules().getIncludes().add("*:*"); } } private static Set<Artifact> dependenciesWithScope(final Set<Artifact> dependencies, String scope) { Set<Artifact> result = new LinkedHashSet<Artifact>(); for (Artifact artifact : dependencies) { if (scope.equals(artifact.getScope())) { result.add(artifact); } } return result; } private static Set<Artifact> allBut(Set<Artifact> source, Set<Artifact> exclusions) { Set<Artifact> result = new LinkedHashSet<Artifact>(source); result.removeAll(exclusions); return result; } private static String dependencyKey(Dependency dependency) { return dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getType() + ":" + dependency.getClassifier(); } }